From 74983ce5ec96b787d153cade8d9cc6f430c8aa79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=B1=BC=E5=BC=80=E5=8F=91?= Date: Mon, 20 Apr 2026 16:39:57 +0800 Subject: [PATCH] feat: init meijiaka-zj project from ai-meijiaka template --- .DS_Store | Bin 0 -> 14340 bytes .cursor/rules/config-architecture.mdc | 43 + .../console-2026-04-15T09-07-14-136Z.log | 9 + .../page-2026-04-15T09-07-14-314Z.yml | 31 + .python-version | 1 + AGENTS.md | 784 ++ CLAUDE.md | 518 + docs/.DS_Store | Bin 0 -> 6148 bytes docs/anytocopy-api.md | 357 + docs/anytocopy-integration.md | 128 + docs/app-update-system.md | 2402 +++++ docs/database-design.md | 357 + docs/kling-api-dev.md | 4282 +++++++++ docs/meijiaka-zhijian-final-plan.md | 101 + docs/meijiaka-zhijian-proposal.md | 833 ++ docs/migrate-avatars-to-local.md | 243 + docs/qiniu-kodo-python-sdk-guide.md | 739 ++ docs/semantic-refactoring-plan.md | 425 + docs/unified-async-scheduler.md | 350 + docs/video-generation-flow.md | 342 + docs/volcengine-video-caption-api.md | 201 + python-api/.env.example | 72 + python-api/.gitignore | 75 + python-api/.pre-commit-config.yaml | 44 + python-api/.python-version | 1 + python-api/.qiniu_pythonsdk_hostscache.json | 1 + python-api/Dockerfile | 44 + python-api/Makefile | 140 + python-api/README.md | 166 + python-api/alembic.ini | 150 + python-api/alembic/README | 1 + python-api/alembic/env.py | 77 + python-api/alembic/script.py.mako | 28 + ...ename_avatar_vendor_fields_add_provider.py | 106 + .../d4bd9ad91607_add_avatars_table.py | 55 + ...replace_device_id_with_mobile_in_users_.py | 38 + python-api/app/__init__.py | 0 python-api/app/ai/__init__.py | 0 python-api/app/ai/model_router.py | 417 + python-api/app/ai/prompts/__init__.py | 46 + python-api/app/ai/prompts/cover/cover.txt | 1 + python-api/app/ai/prompts/loader.py | 228 + .../ai/prompts/polish/scene_empty_shot.txt | 13 + .../app/ai/prompts/polish/scene_segment.txt | 13 + .../app/ai/prompts/polish/voiceover.txt | 12 + python-api/app/ai/prompts/script/system.txt | 96 + .../app/ai/prompts/script/system_副本.txt | 114 + python-api/app/ai/prompts/script/user.txt | 10 + python-api/app/ai/providers/__init__.py | 49 + python-api/app/ai/providers/base.py | 140 + .../app/ai/providers/generic_llm_provider.py | 314 + python-api/app/ai/providers/kling_dto.py | 45 + .../app/ai/providers/klingai_provider.py | 1326 +++ .../app/ai/providers/volcengine_provider.py | 464 + python-api/app/api/__init__.py | 0 python-api/app/api/deps.py | 83 + python-api/app/api/v1/__init__.py | 0 python-api/app/api/v1/ai_models.py | 552 ++ python-api/app/api/v1/auth.py | 83 + python-api/app/api/v1/avatar.py | 560 ++ python-api/app/api/v1/caption.py | 374 + python-api/app/api/v1/klingai.py | 1046 ++ python-api/app/api/v1/qiniu.py | 339 + python-api/app/api/v1/router.py | 51 + python-api/app/api/v1/script.py | 187 + python-api/app/api/v1/system.py | 43 + python-api/app/api/v1/tasks.py | 499 + python-api/app/api/v1/video.py | 511 + python-api/app/config.py | 208 + python-api/app/core/__init__.py | 0 python-api/app/core/config_loader.py | 231 + python-api/app/core/exceptions.py | 89 + python-api/app/core/redis_client.py | 41 + python-api/app/core/security.py | 65 + python-api/app/core/token_manager.py | 436 + python-api/app/core/token_manager_example.py | 170 + python-api/app/crud/__init__.py | 19 + python-api/app/crud/avatar.py | 104 + python-api/app/crud/base.py | 127 + python-api/app/crud/model_usage.py | 45 + python-api/app/crud/user.py | 51 + python-api/app/db/__init__.py | 0 python-api/app/db/session.py | 61 + python-api/app/main.py | 180 + python-api/app/models/__init__.py | 20 + python-api/app/models/avatar.py | 140 + python-api/app/models/base.py | 49 + python-api/app/models/model_usage.py | 62 + python-api/app/models/user.py | 41 + python-api/app/scheduler/__init__.py | 6 + python-api/app/scheduler/engine.py | 121 + python-api/app/scheduler/handlers/__init__.py | 3 + .../app/scheduler/handlers/avatar_handler.py | 504 + python-api/app/scheduler/handlers/base.py | 38 + .../app/scheduler/handlers/copy_handler.py | 120 + .../app/scheduler/handlers/image_handler.py | 190 + .../app/scheduler/handlers/script_handler.py | 141 + .../scheduler/handlers/subtitle_handler.py | 141 + .../app/scheduler/handlers/video_handler.py | 425 + python-api/app/scheduler/main.py | 48 + python-api/app/scheduler/models.py | 47 + python-api/app/scheduler/registry.py | 143 + python-api/app/scheduler/slot_manager.py | 63 + python-api/app/schemas/__init__.py | 78 + python-api/app/schemas/auth.py | 36 + python-api/app/schemas/avatar.py | 33 + python-api/app/schemas/caption.py | 98 + python-api/app/schemas/common.py | 78 + python-api/app/schemas/enums.py | 49 + python-api/app/schemas/job.py | 73 + python-api/app/schemas/script.py | 98 + python-api/app/schemas/segment.py | 48 + python-api/app/services/__init__.py | 14 + python-api/app/services/ai_response_utils.py | 331 + python-api/app/services/anytocopy_service.py | 350 + python-api/app/services/ass_generator.py | 113 + .../app/services/kling_video_service.py | 811 ++ python-api/app/services/qiniu_service.py | 486 + python-api/app/services/script_service.py | 662 ++ .../services/volcengine_caption_service.py | 566 ++ python-api/check_all_shots.py | 40 + python-api/check_task_status.py | 28 + python-api/config/ai_models.yaml | 239 + python-api/docker-compose.yml | 98 + python-api/docs/token_manager.md | 300 + python-api/generate_kling_token.py | 46 + python-api/mark_timeout_failed.py | 42 + python-api/pyproject.toml | 126 + python-api/query_kling_task.py | 97 + scripts/add_video_path_to_segments.py | 96 + scripts/fix_segments_with_videos.py | 101 + scripts/recover_video_paths.py | 156 + tauri-app/.env | 3 + tauri-app/.gitignore | 24 + tauri-app/.prettierrc | 15 + tauri-app/.stylelintrc.json | 22 + tauri-app/.vscode/extensions.json | 8 + tauri-app/AGENTS.md | 249 + tauri-app/README.md | 7 + tauri-app/assets/subtitle-template.ass | 19 + tauri-app/docs/data-architecture.md | 232 + tauri-app/docs/spacing-guide.md | 95 + tauri-app/eslint.config.js | 57 + tauri-app/index.html | 24 + tauri-app/package-lock.json | 8172 ++++++++++++++++ tauri-app/package.json | 61 + tauri-app/public/assets/logo.png | Bin 0 -> 26518 bytes .../audio/presets/ai_huangyaoshi_712.mp3 | Bin 0 -> 54740 bytes .../audio/presets/ai_taiwan_man2_speech02.mp3 | Bin 0 -> 35156 bytes .../audio/presets/chat1_female_new-3.mp3 | Bin 0 -> 62228 bytes .../public/audio/presets/chengshu_jiejie.mp3 | Bin 0 -> 57620 bytes .../audio/presets/girlfriend_2_speech02.mp3 | Bin 0 -> 56468 bytes .../public/audio/presets/tiexin_nanyou.mp3 | Bin 0 -> 49556 bytes .../public/audio/presets/yizhipiannan-v1.mp3 | Bin 0 -> 57939 bytes .../public/audio/presets/you_pingjing.mp3 | Bin 0 -> 52436 bytes tauri-app/public/fonts/DouyinSansBold.ttf | 1 + tauri-app/public/tauri.svg | 6 + tauri-app/public/vite.svg | 1 + tauri-app/src-tauri/.gitignore | 7 + tauri-app/src-tauri/Cargo.toml | 43 + tauri-app/src-tauri/DEPS_ANALYSIS.md | 108 + tauri-app/src-tauri/DEPS_OPTIMIZATION.md | 159 + tauri-app/src-tauri/build.rs | 3 + tauri-app/src-tauri/capabilities/default.json | 41 + tauri-app/src-tauri/fonts/DouyinSansBold.ttf | Bin 0 -> 1919404 bytes tauri-app/src-tauri/icons/128x128.png | Bin 0 -> 3512 bytes tauri-app/src-tauri/icons/128x128@2x.png | Bin 0 -> 7012 bytes tauri-app/src-tauri/icons/32x32.png | Bin 0 -> 974 bytes .../src-tauri/icons/Square107x107Logo.png | Bin 0 -> 2863 bytes .../src-tauri/icons/Square142x142Logo.png | Bin 0 -> 3858 bytes .../src-tauri/icons/Square150x150Logo.png | Bin 0 -> 3966 bytes .../src-tauri/icons/Square284x284Logo.png | Bin 0 -> 7737 bytes tauri-app/src-tauri/icons/Square30x30Logo.png | Bin 0 -> 903 bytes .../src-tauri/icons/Square310x310Logo.png | Bin 0 -> 8591 bytes tauri-app/src-tauri/icons/Square44x44Logo.png | Bin 0 -> 1299 bytes tauri-app/src-tauri/icons/Square71x71Logo.png | Bin 0 -> 2011 bytes tauri-app/src-tauri/icons/Square89x89Logo.png | Bin 0 -> 2468 bytes tauri-app/src-tauri/icons/StoreLogo.png | Bin 0 -> 1523 bytes tauri-app/src-tauri/icons/icon.icns | Bin 0 -> 98451 bytes tauri-app/src-tauri/icons/icon.ico | Bin 0 -> 86642 bytes tauri-app/src-tauri/icons/icon.png | Bin 0 -> 14183 bytes tauri-app/src-tauri/src/api_proxy.rs | 114 + tauri-app/src-tauri/src/auth.rs | 25 + tauri-app/src-tauri/src/avatar_cache.rs | 110 + tauri-app/src-tauri/src/commands/asset.rs | 62 + .../src-tauri/src/commands/auth_state.rs | 55 + tauri-app/src-tauri/src/commands/avatar.rs | 39 + tauri-app/src-tauri/src/commands/mod.rs | 11 + tauri-app/src-tauri/src/commands/product.rs | 295 + tauri-app/src-tauri/src/commands/project.rs | 137 + tauri-app/src-tauri/src/ffmpeg_cmd.rs | 332 + tauri-app/src-tauri/src/lib.rs | 264 + tauri-app/src-tauri/src/main.rs | 6 + tauri-app/src-tauri/src/storage/auth.rs | 45 + tauri-app/src-tauri/src/storage/avatar.rs | 26 + tauri-app/src-tauri/src/storage/cache.rs | 451 + tauri-app/src-tauri/src/storage/engine.rs | 169 + tauri-app/src-tauri/src/storage/mod.rs | 22 + tauri-app/src-tauri/src/storage/paths.rs | 121 + tauri-app/src-tauri/src/storage/project.rs | 250 + tauri-app/src-tauri/src/utils.rs | 21 + tauri-app/src-tauri/src/video_processing.rs | 176 + tauri-app/src-tauri/tauri.conf.json | 61 + tauri-app/src/App.css | 21 + tauri-app/src/App.tsx | 163 + tauri-app/src/__tests__/setup.ts | 30 + tauri-app/src/api/adapters/projectAdapter.ts | 61 + tauri-app/src/api/adapters/scriptAdapter.ts | 38 + tauri-app/src/api/client.ts | 212 + tauri-app/src/api/generated/openapi.json | 8498 +++++++++++++++++ tauri-app/src/api/generated/schema.ts | 7148 ++++++++++++++ tauri-app/src/api/modules/avatar.ts | 250 + tauri-app/src/api/modules/localStorage.ts | 306 + tauri-app/src/api/modules/project.ts | 130 + tauri-app/src/api/modules/script.ts | 110 + tauri-app/src/api/modules/task.ts | 56 + tauri-app/src/api/modules/videoComposite.ts | 28 + tauri-app/src/api/types.ts | 58 + tauri-app/src/assets/react.svg | 1 + .../ErrorBoundary/ErrorBoundary.css | 65 + .../ErrorBoundary/ErrorBoundary.tsx | 128 + .../src/components/ErrorBoundary/index.ts | 2 + tauri-app/src/components/Layout/Sidebar.css | 253 + tauri-app/src/components/Layout/Sidebar.tsx | 203 + .../components/Modal/AvatarUploadModal.css | 115 + .../components/Modal/AvatarUploadModal.tsx | 470 + .../src/components/Modal/ConfirmModal.css | 159 + .../src/components/Modal/ConfirmModal.tsx | 141 + tauri-app/src/components/Modal/Modal.css | 59 + tauri-app/src/components/Modal/Modal.tsx | 85 + .../ProgressModal/ProgressModal.css | 279 + .../ProgressModal/ProgressModal.tsx | 159 + .../src/components/ShotStats/ShotStats.css | 112 + .../src/components/ShotStats/ShotStats.tsx | 91 + tauri-app/src/components/ShotStats/index.ts | 2 + tauri-app/src/components/Slider/Slider.css | 95 + tauri-app/src/components/Toast/Toast.css | 95 + .../src/components/Toast/ToastContainer.tsx | 87 + tauri-app/src/hooks/useAssJsRenderer.ts | 107 + tauri-app/src/hooks/useAvatarCache.ts | 315 + tauri-app/src/hooks/useAvatarLibrary.ts | 123 + tauri-app/src/hooks/useLocalImage.ts | 96 + tauri-app/src/hooks/useLocalVideo.ts | 118 + tauri-app/src/hooks/useModelHealth.ts | 37 + tauri-app/src/hooks/usePerformanceMonitor.ts | 86 + tauri-app/src/hooks/useSubtitleAlignment.ts | 390 + tauri-app/src/hooks/useTask.ts | 256 + tauri-app/src/hooks/useVideoGeneration.ts | 319 + tauri-app/src/main.tsx | 14 + .../pages/ContentManagement/AvatarCard.tsx | 236 + .../pages/ContentManagement/AvatarClone.tsx | 274 + .../ContentManagement/ContentManagement.css | 1284 +++ .../src/pages/ContentManagement/MyWorks.tsx | 360 + tauri-app/src/pages/Login/Login.css | 350 + tauri-app/src/pages/Login/Login.tsx | 207 + tauri-app/src/pages/Profile/Profile.tsx | 64 + tauri-app/src/pages/Profile/UsageDetail.tsx | 137 + tauri-app/src/pages/Settings/AboutUs.tsx | 44 + tauri-app/src/pages/Settings/SystemUpdate.tsx | 62 + .../src/pages/Settings/ThemeSettings.tsx | 192 + .../src/pages/VideoCreation/CoverDesign.css | 373 + .../src/pages/VideoCreation/CoverDesign.tsx | 418 + .../pages/VideoCreation/ScriptCreation.css | 272 + .../pages/VideoCreation/ScriptCreation.tsx | 653 ++ .../pages/VideoCreation/SubtitleBurning.css | 604 ++ .../pages/VideoCreation/SubtitleBurning.tsx | 573 ++ .../pages/VideoCreation/VideoComposite.tsx | 365 + .../src/pages/VideoCreation/VideoCreation.css | 1779 ++++ .../pages/VideoCreation/VideoGeneration.css | 404 + .../pages/VideoCreation/VideoGeneration.tsx | 919 ++ tauri-app/src/pages/VideoCreation/index.tsx | 286 + .../src/store/__tests__/authStore.test.tsx | 74 + .../store/__tests__/settingsStore.test.tsx | 29 + tauri-app/src/store/authStore.ts | 218 + tauri-app/src/store/index.ts | 26 + tauri-app/src/store/progressStore.ts | 87 + tauri-app/src/store/projectStore.ts | 517 + tauri-app/src/store/settingsStore.ts | 55 + tauri-app/src/store/taskStore.ts | 155 + tauri-app/src/store/uiStore.ts | 80 + tauri-app/src/styles/global.css | 364 + tauri-app/src/styles/variables.css | 141 + tauri-app/src/utils/assGenerator.ts | 273 + tauri-app/src/utils/avatarStorage.ts | 174 + tauri-app/src/utils/env.ts | 16 + tauri-app/src/utils/fileHash.ts | 97 + tauri-app/src/vite-env.d.ts | 1 + tauri-app/tsconfig.json | 26 + tauri-app/tsconfig.node.json | 10 + tauri-app/vite.config.ts | 36 + tauri-app/vitest.config.ts | 27 + 291 files changed, 76164 insertions(+) create mode 100644 .DS_Store create mode 100644 .cursor/rules/config-architecture.mdc create mode 100644 .playwright-mcp/console-2026-04-15T09-07-14-136Z.log create mode 100644 .playwright-mcp/page-2026-04-15T09-07-14-314Z.yml create mode 100644 .python-version create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 docs/.DS_Store create mode 100644 docs/anytocopy-api.md create mode 100644 docs/anytocopy-integration.md create mode 100644 docs/app-update-system.md create mode 100644 docs/database-design.md create mode 100644 docs/kling-api-dev.md create mode 100644 docs/meijiaka-zhijian-final-plan.md create mode 100644 docs/meijiaka-zhijian-proposal.md create mode 100644 docs/migrate-avatars-to-local.md create mode 100644 docs/qiniu-kodo-python-sdk-guide.md create mode 100644 docs/semantic-refactoring-plan.md create mode 100644 docs/unified-async-scheduler.md create mode 100644 docs/video-generation-flow.md create mode 100644 docs/volcengine-video-caption-api.md create mode 100644 python-api/.env.example create mode 100644 python-api/.gitignore create mode 100644 python-api/.pre-commit-config.yaml create mode 100644 python-api/.python-version create mode 100644 python-api/.qiniu_pythonsdk_hostscache.json create mode 100644 python-api/Dockerfile create mode 100644 python-api/Makefile create mode 100644 python-api/README.md create mode 100644 python-api/alembic.ini create mode 100644 python-api/alembic/README create mode 100644 python-api/alembic/env.py create mode 100644 python-api/alembic/script.py.mako create mode 100644 python-api/alembic/versions/451756e6a43e_rename_avatar_vendor_fields_add_provider.py create mode 100644 python-api/alembic/versions/d4bd9ad91607_add_avatars_table.py create mode 100644 python-api/alembic/versions/fb1be66e804a_replace_device_id_with_mobile_in_users_.py create mode 100644 python-api/app/__init__.py create mode 100644 python-api/app/ai/__init__.py create mode 100644 python-api/app/ai/model_router.py create mode 100644 python-api/app/ai/prompts/__init__.py create mode 100644 python-api/app/ai/prompts/cover/cover.txt create mode 100644 python-api/app/ai/prompts/loader.py create mode 100644 python-api/app/ai/prompts/polish/scene_empty_shot.txt create mode 100644 python-api/app/ai/prompts/polish/scene_segment.txt create mode 100644 python-api/app/ai/prompts/polish/voiceover.txt create mode 100644 python-api/app/ai/prompts/script/system.txt create mode 100644 python-api/app/ai/prompts/script/system_副本.txt create mode 100644 python-api/app/ai/prompts/script/user.txt create mode 100644 python-api/app/ai/providers/__init__.py create mode 100644 python-api/app/ai/providers/base.py create mode 100644 python-api/app/ai/providers/generic_llm_provider.py create mode 100644 python-api/app/ai/providers/kling_dto.py create mode 100644 python-api/app/ai/providers/klingai_provider.py create mode 100644 python-api/app/ai/providers/volcengine_provider.py create mode 100644 python-api/app/api/__init__.py create mode 100644 python-api/app/api/deps.py create mode 100644 python-api/app/api/v1/__init__.py create mode 100644 python-api/app/api/v1/ai_models.py create mode 100644 python-api/app/api/v1/auth.py create mode 100644 python-api/app/api/v1/avatar.py create mode 100644 python-api/app/api/v1/caption.py create mode 100644 python-api/app/api/v1/klingai.py create mode 100644 python-api/app/api/v1/qiniu.py create mode 100644 python-api/app/api/v1/router.py create mode 100644 python-api/app/api/v1/script.py create mode 100644 python-api/app/api/v1/system.py create mode 100644 python-api/app/api/v1/tasks.py create mode 100644 python-api/app/api/v1/video.py create mode 100644 python-api/app/config.py create mode 100644 python-api/app/core/__init__.py create mode 100644 python-api/app/core/config_loader.py create mode 100644 python-api/app/core/exceptions.py create mode 100644 python-api/app/core/redis_client.py create mode 100644 python-api/app/core/security.py create mode 100644 python-api/app/core/token_manager.py create mode 100644 python-api/app/core/token_manager_example.py create mode 100644 python-api/app/crud/__init__.py create mode 100644 python-api/app/crud/avatar.py create mode 100644 python-api/app/crud/base.py create mode 100644 python-api/app/crud/model_usage.py create mode 100644 python-api/app/crud/user.py create mode 100644 python-api/app/db/__init__.py create mode 100644 python-api/app/db/session.py create mode 100644 python-api/app/main.py create mode 100644 python-api/app/models/__init__.py create mode 100644 python-api/app/models/avatar.py create mode 100644 python-api/app/models/base.py create mode 100644 python-api/app/models/model_usage.py create mode 100644 python-api/app/models/user.py create mode 100644 python-api/app/scheduler/__init__.py create mode 100644 python-api/app/scheduler/engine.py create mode 100644 python-api/app/scheduler/handlers/__init__.py create mode 100644 python-api/app/scheduler/handlers/avatar_handler.py create mode 100644 python-api/app/scheduler/handlers/base.py create mode 100644 python-api/app/scheduler/handlers/copy_handler.py create mode 100644 python-api/app/scheduler/handlers/image_handler.py create mode 100644 python-api/app/scheduler/handlers/script_handler.py create mode 100644 python-api/app/scheduler/handlers/subtitle_handler.py create mode 100644 python-api/app/scheduler/handlers/video_handler.py create mode 100644 python-api/app/scheduler/main.py create mode 100644 python-api/app/scheduler/models.py create mode 100644 python-api/app/scheduler/registry.py create mode 100644 python-api/app/scheduler/slot_manager.py create mode 100644 python-api/app/schemas/__init__.py create mode 100644 python-api/app/schemas/auth.py create mode 100644 python-api/app/schemas/avatar.py create mode 100644 python-api/app/schemas/caption.py create mode 100644 python-api/app/schemas/common.py create mode 100644 python-api/app/schemas/enums.py create mode 100644 python-api/app/schemas/job.py create mode 100644 python-api/app/schemas/script.py create mode 100644 python-api/app/schemas/segment.py create mode 100644 python-api/app/services/__init__.py create mode 100644 python-api/app/services/ai_response_utils.py create mode 100644 python-api/app/services/anytocopy_service.py create mode 100644 python-api/app/services/ass_generator.py create mode 100644 python-api/app/services/kling_video_service.py create mode 100644 python-api/app/services/qiniu_service.py create mode 100644 python-api/app/services/script_service.py create mode 100644 python-api/app/services/volcengine_caption_service.py create mode 100644 python-api/check_all_shots.py create mode 100644 python-api/check_task_status.py create mode 100644 python-api/config/ai_models.yaml create mode 100644 python-api/docker-compose.yml create mode 100644 python-api/docs/token_manager.md create mode 100644 python-api/generate_kling_token.py create mode 100644 python-api/mark_timeout_failed.py create mode 100644 python-api/pyproject.toml create mode 100644 python-api/query_kling_task.py create mode 100644 scripts/add_video_path_to_segments.py create mode 100644 scripts/fix_segments_with_videos.py create mode 100644 scripts/recover_video_paths.py create mode 100644 tauri-app/.env create mode 100644 tauri-app/.gitignore create mode 100644 tauri-app/.prettierrc create mode 100644 tauri-app/.stylelintrc.json create mode 100644 tauri-app/.vscode/extensions.json create mode 100644 tauri-app/AGENTS.md create mode 100644 tauri-app/README.md create mode 100644 tauri-app/assets/subtitle-template.ass create mode 100644 tauri-app/docs/data-architecture.md create mode 100644 tauri-app/docs/spacing-guide.md create mode 100644 tauri-app/eslint.config.js create mode 100644 tauri-app/index.html create mode 100644 tauri-app/package-lock.json create mode 100644 tauri-app/package.json create mode 100644 tauri-app/public/assets/logo.png create mode 100644 tauri-app/public/audio/presets/ai_huangyaoshi_712.mp3 create mode 100644 tauri-app/public/audio/presets/ai_taiwan_man2_speech02.mp3 create mode 100644 tauri-app/public/audio/presets/chat1_female_new-3.mp3 create mode 100644 tauri-app/public/audio/presets/chengshu_jiejie.mp3 create mode 100644 tauri-app/public/audio/presets/girlfriend_2_speech02.mp3 create mode 100644 tauri-app/public/audio/presets/tiexin_nanyou.mp3 create mode 100644 tauri-app/public/audio/presets/yizhipiannan-v1.mp3 create mode 100644 tauri-app/public/audio/presets/you_pingjing.mp3 create mode 120000 tauri-app/public/fonts/DouyinSansBold.ttf create mode 100644 tauri-app/public/tauri.svg create mode 100644 tauri-app/public/vite.svg create mode 100644 tauri-app/src-tauri/.gitignore create mode 100644 tauri-app/src-tauri/Cargo.toml create mode 100644 tauri-app/src-tauri/DEPS_ANALYSIS.md create mode 100644 tauri-app/src-tauri/DEPS_OPTIMIZATION.md create mode 100644 tauri-app/src-tauri/build.rs create mode 100644 tauri-app/src-tauri/capabilities/default.json create mode 100644 tauri-app/src-tauri/fonts/DouyinSansBold.ttf create mode 100644 tauri-app/src-tauri/icons/128x128.png create mode 100644 tauri-app/src-tauri/icons/128x128@2x.png create mode 100644 tauri-app/src-tauri/icons/32x32.png create mode 100644 tauri-app/src-tauri/icons/Square107x107Logo.png create mode 100644 tauri-app/src-tauri/icons/Square142x142Logo.png create mode 100644 tauri-app/src-tauri/icons/Square150x150Logo.png create mode 100644 tauri-app/src-tauri/icons/Square284x284Logo.png create mode 100644 tauri-app/src-tauri/icons/Square30x30Logo.png create mode 100644 tauri-app/src-tauri/icons/Square310x310Logo.png create mode 100644 tauri-app/src-tauri/icons/Square44x44Logo.png create mode 100644 tauri-app/src-tauri/icons/Square71x71Logo.png create mode 100644 tauri-app/src-tauri/icons/Square89x89Logo.png create mode 100644 tauri-app/src-tauri/icons/StoreLogo.png create mode 100644 tauri-app/src-tauri/icons/icon.icns create mode 100644 tauri-app/src-tauri/icons/icon.ico create mode 100644 tauri-app/src-tauri/icons/icon.png create mode 100644 tauri-app/src-tauri/src/api_proxy.rs create mode 100644 tauri-app/src-tauri/src/auth.rs create mode 100644 tauri-app/src-tauri/src/avatar_cache.rs create mode 100644 tauri-app/src-tauri/src/commands/asset.rs create mode 100644 tauri-app/src-tauri/src/commands/auth_state.rs create mode 100644 tauri-app/src-tauri/src/commands/avatar.rs create mode 100644 tauri-app/src-tauri/src/commands/mod.rs create mode 100644 tauri-app/src-tauri/src/commands/product.rs create mode 100644 tauri-app/src-tauri/src/commands/project.rs create mode 100644 tauri-app/src-tauri/src/ffmpeg_cmd.rs create mode 100644 tauri-app/src-tauri/src/lib.rs create mode 100644 tauri-app/src-tauri/src/main.rs create mode 100644 tauri-app/src-tauri/src/storage/auth.rs create mode 100644 tauri-app/src-tauri/src/storage/avatar.rs create mode 100644 tauri-app/src-tauri/src/storage/cache.rs create mode 100644 tauri-app/src-tauri/src/storage/engine.rs create mode 100644 tauri-app/src-tauri/src/storage/mod.rs create mode 100644 tauri-app/src-tauri/src/storage/paths.rs create mode 100644 tauri-app/src-tauri/src/storage/project.rs create mode 100644 tauri-app/src-tauri/src/utils.rs create mode 100644 tauri-app/src-tauri/src/video_processing.rs create mode 100644 tauri-app/src-tauri/tauri.conf.json create mode 100644 tauri-app/src/App.css create mode 100644 tauri-app/src/App.tsx create mode 100644 tauri-app/src/__tests__/setup.ts create mode 100644 tauri-app/src/api/adapters/projectAdapter.ts create mode 100644 tauri-app/src/api/adapters/scriptAdapter.ts create mode 100644 tauri-app/src/api/client.ts create mode 100644 tauri-app/src/api/generated/openapi.json create mode 100644 tauri-app/src/api/generated/schema.ts create mode 100644 tauri-app/src/api/modules/avatar.ts create mode 100644 tauri-app/src/api/modules/localStorage.ts create mode 100644 tauri-app/src/api/modules/project.ts create mode 100644 tauri-app/src/api/modules/script.ts create mode 100644 tauri-app/src/api/modules/task.ts create mode 100644 tauri-app/src/api/modules/videoComposite.ts create mode 100644 tauri-app/src/api/types.ts create mode 100644 tauri-app/src/assets/react.svg create mode 100644 tauri-app/src/components/ErrorBoundary/ErrorBoundary.css create mode 100644 tauri-app/src/components/ErrorBoundary/ErrorBoundary.tsx create mode 100644 tauri-app/src/components/ErrorBoundary/index.ts create mode 100644 tauri-app/src/components/Layout/Sidebar.css create mode 100644 tauri-app/src/components/Layout/Sidebar.tsx create mode 100644 tauri-app/src/components/Modal/AvatarUploadModal.css create mode 100644 tauri-app/src/components/Modal/AvatarUploadModal.tsx create mode 100644 tauri-app/src/components/Modal/ConfirmModal.css create mode 100644 tauri-app/src/components/Modal/ConfirmModal.tsx create mode 100644 tauri-app/src/components/Modal/Modal.css create mode 100644 tauri-app/src/components/Modal/Modal.tsx create mode 100644 tauri-app/src/components/ProgressModal/ProgressModal.css create mode 100644 tauri-app/src/components/ProgressModal/ProgressModal.tsx create mode 100644 tauri-app/src/components/ShotStats/ShotStats.css create mode 100644 tauri-app/src/components/ShotStats/ShotStats.tsx create mode 100644 tauri-app/src/components/ShotStats/index.ts create mode 100644 tauri-app/src/components/Slider/Slider.css create mode 100644 tauri-app/src/components/Toast/Toast.css create mode 100644 tauri-app/src/components/Toast/ToastContainer.tsx create mode 100644 tauri-app/src/hooks/useAssJsRenderer.ts create mode 100644 tauri-app/src/hooks/useAvatarCache.ts create mode 100644 tauri-app/src/hooks/useAvatarLibrary.ts create mode 100644 tauri-app/src/hooks/useLocalImage.ts create mode 100644 tauri-app/src/hooks/useLocalVideo.ts create mode 100644 tauri-app/src/hooks/useModelHealth.ts create mode 100644 tauri-app/src/hooks/usePerformanceMonitor.ts create mode 100644 tauri-app/src/hooks/useSubtitleAlignment.ts create mode 100644 tauri-app/src/hooks/useTask.ts create mode 100644 tauri-app/src/hooks/useVideoGeneration.ts create mode 100644 tauri-app/src/main.tsx create mode 100644 tauri-app/src/pages/ContentManagement/AvatarCard.tsx create mode 100644 tauri-app/src/pages/ContentManagement/AvatarClone.tsx create mode 100644 tauri-app/src/pages/ContentManagement/ContentManagement.css create mode 100644 tauri-app/src/pages/ContentManagement/MyWorks.tsx create mode 100644 tauri-app/src/pages/Login/Login.css create mode 100644 tauri-app/src/pages/Login/Login.tsx create mode 100644 tauri-app/src/pages/Profile/Profile.tsx create mode 100644 tauri-app/src/pages/Profile/UsageDetail.tsx create mode 100644 tauri-app/src/pages/Settings/AboutUs.tsx create mode 100644 tauri-app/src/pages/Settings/SystemUpdate.tsx create mode 100644 tauri-app/src/pages/Settings/ThemeSettings.tsx create mode 100644 tauri-app/src/pages/VideoCreation/CoverDesign.css create mode 100644 tauri-app/src/pages/VideoCreation/CoverDesign.tsx create mode 100644 tauri-app/src/pages/VideoCreation/ScriptCreation.css create mode 100644 tauri-app/src/pages/VideoCreation/ScriptCreation.tsx create mode 100644 tauri-app/src/pages/VideoCreation/SubtitleBurning.css create mode 100644 tauri-app/src/pages/VideoCreation/SubtitleBurning.tsx create mode 100644 tauri-app/src/pages/VideoCreation/VideoComposite.tsx create mode 100644 tauri-app/src/pages/VideoCreation/VideoCreation.css create mode 100644 tauri-app/src/pages/VideoCreation/VideoGeneration.css create mode 100644 tauri-app/src/pages/VideoCreation/VideoGeneration.tsx create mode 100644 tauri-app/src/pages/VideoCreation/index.tsx create mode 100644 tauri-app/src/store/__tests__/authStore.test.tsx create mode 100644 tauri-app/src/store/__tests__/settingsStore.test.tsx create mode 100644 tauri-app/src/store/authStore.ts create mode 100644 tauri-app/src/store/index.ts create mode 100644 tauri-app/src/store/progressStore.ts create mode 100644 tauri-app/src/store/projectStore.ts create mode 100644 tauri-app/src/store/settingsStore.ts create mode 100644 tauri-app/src/store/taskStore.ts create mode 100644 tauri-app/src/store/uiStore.ts create mode 100644 tauri-app/src/styles/global.css create mode 100644 tauri-app/src/styles/variables.css create mode 100644 tauri-app/src/utils/assGenerator.ts create mode 100644 tauri-app/src/utils/avatarStorage.ts create mode 100644 tauri-app/src/utils/env.ts create mode 100644 tauri-app/src/utils/fileHash.ts create mode 100644 tauri-app/src/vite-env.d.ts create mode 100644 tauri-app/tsconfig.json create mode 100644 tauri-app/tsconfig.node.json create mode 100644 tauri-app/vite.config.ts create mode 100644 tauri-app/vitest.config.ts diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..62071709ba38e1be772d134f8725712fd40ca65a GIT binary patch literal 14340 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 J|BnRz|3B92>d^oI literal 0 HcmV?d00001 diff --git a/.cursor/rules/config-architecture.mdc b/.cursor/rules/config-architecture.mdc new file mode 100644 index 0000000..669f8dc --- /dev/null +++ b/.cursor/rules/config-architecture.mdc @@ -0,0 +1,43 @@ +# 配置架构规则 + +description: 配置管理架构规范 + +## 规则 + +### 配置读取 +- 所有配置必须通过 `from app.config import get_settings` 读取 +- 禁止直接使用 `os.getenv()` 或 `os.environ.get()` +- 禁止在服务层、API 层直接使用环境变量 + +### 添加新配置 +1. 在 `app/config.py` 的 `Settings` 类中定义字段 +2. 使用 `Field(default=..., description="...")` 提供默认值和说明 +3. 敏感信息使用 `str | None = None` 类型 +4. 更新 `.env.example` 文档 + +### 在服务中使用配置 +```python +from app.config import get_settings + +def some_function(): + settings = get_settings() + api_key = settings.SOME_API_KEY +``` + +### 禁止的写法 +```python +import os + +# ❌ 禁止 +api_key = os.getenv("SOME_API_KEY") +api_key = os.environ.get("SOME_API_KEY") +``` + +### 推荐的写法 +```python +from app.config import get_settings + +# ✅ 正确 +settings = get_settings() +api_key = settings.SOME_API_KEY +``` diff --git a/.playwright-mcp/console-2026-04-15T09-07-14-136Z.log b/.playwright-mcp/console-2026-04-15T09-07-14-136Z.log new file mode 100644 index 0000000..d3d34e8 --- /dev/null +++ b/.playwright-mcp/console-2026-04-15T09-07-14-136Z.log @@ -0,0 +1,9 @@ +[ 117ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:1420/node_modules/.vite/deps/react-dom_client.js?v=a15e99c2:20102 +[ 148ms] [LOG] [ScriptCreation] segments changed: 0 @ http://localhost:1420/src/pages/VideoCreation/ScriptCreation.tsx:52 +[ 148ms] [LOG] [ScriptCreation] segments changed: 0 @ http://localhost:1420/src/pages/VideoCreation/ScriptCreation.tsx:52 +[ 152ms] [ERROR] [authStore] 加载认证状态失败: TypeError: Cannot read properties of undefined (reading 'invoke') + at invoke (http://localhost:1420/node_modules/.vite/deps/chunk-G7S6KQDI.js?v=a15e99c2:109:37) + at loadFromStorage (http://localhost:1420/src/store/authStore.ts:72:28) @ http://localhost:1420/src/store/authStore.ts:83 +[ 152ms] [ERROR] [authStore] 加载认证状态失败: TypeError: Cannot read properties of undefined (reading 'invoke') + at invoke (http://localhost:1420/node_modules/.vite/deps/chunk-G7S6KQDI.js?v=a15e99c2:109:37) + at loadFromStorage (http://localhost:1420/src/store/authStore.ts:72:28) @ http://localhost:1420/src/store/authStore.ts:83 diff --git a/.playwright-mcp/page-2026-04-15T09-07-14-314Z.yml b/.playwright-mcp/page-2026-04-15T09-07-14-314Z.yml new file mode 100644 index 0000000..dc59250 --- /dev/null +++ b/.playwright-mcp/page-2026-04-15T09-07-14-314Z.yml @@ -0,0 +1,31 @@ +- generic [ref=e4]: + - generic [ref=e5]: + - generic [ref=e6]: + - img "美家卡 智影" [ref=e7] + - generic [ref=e8]: 美家卡 智影 + - paragraph [ref=e9]: AI 驱动的智能视频创作平台 + - generic [ref=e10]: + - heading "欢迎登录" [level=2] [ref=e11] + - paragraph [ref=e12]: 使用手机号验证码快速登录 + - generic [ref=e13]: + - generic [ref=e14]: + - generic [ref=e15]: 手机号 + - generic [ref=e16]: + - generic [ref=e17]: "+86" + - textbox "请输入手机号" [active] [ref=e18] + - generic [ref=e19]: + - generic [ref=e20]: 验证码 + - generic [ref=e21]: + - textbox "请输入验证码" [ref=e22] + - button "获取验证码" [disabled] [ref=e23] + - button "登录" [disabled] [ref=e24] + - generic [ref=e25]: + - checkbox "我已阅读并同意《用户服务协议》和《隐私政策》" [ref=e26] [cursor=pointer] + - generic [ref=e27] [cursor=pointer]: + - text: 我已阅读并同意 + - link "《用户服务协议》" [ref=e28]: + - /url: "#" + - text: 和 + - link "《隐私政策》" [ref=e29]: + - /url: "#" + - generic [ref=e30]: meijiaka.cn \ No newline at end of file diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..bb5e9e0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,784 @@ + +# 美家卡智影 (Meijiaka AI Video) - AI 视频创作平台 + +## 项目概述 + +美家卡智影是一个 AI 驱动的视频创作桌面应用,采用 **Tauri + React + FastAPI** 混合架构。用户可以通过 AI 生成脚本、创建数字人视频,最终合成完整的营销视频。 + +### 核心功能 + +- **AI 脚本生成**: 基于 LLM 自动生成视频脚本和分镜 +- **数字人视频**: 基于 KlingAI 创建数字人视频片段 +- **字幕生成**: 基于火山引擎豆包语音自动生成字幕并压制到视频 +- **封面制作**: 提取视频首帧并叠加字幕样式生成封面 +- **视频合成**: 本地 FFmpeg 处理视频拼接、音频混流、导出成品 +- **形象克隆**: 基于 KlingAI 的自定义数字人形象管理 +- **项目管理**: 项目数据本地 JSON 文件存储,认证状态云端同步 + +## 项目结构 + +``` +ai-meijiaka/ +├── python-api/ # FastAPI 后端服务(AI 代理 + 认证 + 任务调度) +│ ├── app/ +│ │ ├── api/v1/ # API 路由 (REST): auth, script, ai_models, klingai, +│ │ │ # qiniu, video, avatar, system, +│ │ │ # caption, tasks +│ │ ├── ai/ # AI 模型路由、Provider、提示词模板 +│ │ ├── core/ # 安全、配置加载、Token管理器、Redis客户端、异常处理 +│ │ ├── crud/ # 数据访问层(users, model_usage, avatar) +│ │ ├── db/ # 数据库配置(PostgreSQL + asyncpg + SQLAlchemy 2.0) +│ │ ├── models/ # SQLAlchemy 模型(users, model_usage_logs, avatars) +│ │ ├── schemas/ # Pydantic 校验模型 +│ │ ├── services/ # AI 服务代理、DTO标准化、七牛/字幕/视频服务 +│ │ ├── scheduler/ # Async Engine 异步任务调度(video, image, script, +│ │ │ # subtitle, copy, avatar_clone) +│ │ ├── config.py # Pydantic Settings 配置管理 +│ │ └── main.py # FastAPI 入口(含生命周期管理) +│ ├── config/ # AI 模型配置文件(ai_models.yaml),支持热重载 +│ ├── alembic/ # 数据库迁移 +│ ├── scripts/ # 初始化/测试脚本 +│ ├── pyproject.toml # Python 依赖和工具配置 +│ ├── requirements.lock # uv 锁定依赖版本 +│ ├── Makefile # 常用命令封装 +│ ├── docker-compose.yml +│ └── Dockerfile +│ +├── tauri-app/ # Tauri 桌面应用(业务数据本地存储) +│ ├── src/ # React 前端源码 +│ │ ├── api/ +│ │ │ ├── adapters/ # 数据转换层(前后端字段映射) +│ │ │ ├── generated/ # OpenAPI 自动生成类型(只读) +│ │ │ ├── modules/ # API 模块封装(HTTP + IPC) +│ │ │ ├── client.ts # HTTP 客户端(自动 camelCase↔snake_case) +│ │ │ ├── types.ts # 手写核心类型 +│ │ │ └── ipc.ts # Tauri IPC 调用封装 +│ │ ├── components/ # 可复用组件 +│ │ ├── pages/ # 页面组件 +│ │ ├── store/ # Zustand 状态管理(+ Immer + persist) +│ │ ├── hooks/ # 自定义 React Hooks +│ │ ├── styles/ # 全局 CSS 变量、主题 +│ │ └── utils/ # 工具函数 +│ ├── src-tauri/ # Rust 后端源码 +│ │ ├── src/ +│ │ │ ├── lib.rs # Tauri 应用入口,命令注册 +│ │ │ ├── ffmpeg_cmd.rs # FFmpeg 命令封装 +│ │ │ ├── video_processing.rs # 视频合成业务逻辑 +│ │ │ ├── storage/ # 本地存储引擎(原子写入、文件锁、路径净化) +│ │ │ ├── commands/ # IPC 命令按领域拆分(project/asset/auth/avatar) +│ │ │ ├── api_proxy.rs # Python API 代理转发 +│ │ │ ├── auth.rs # 认证命令(已迁移至 commands/auth_state.rs) +│ │ │ ├── avatar_cache.rs # 头像缓存管理 +│ │ │ └── utils.rs # 通用工具函数 +│ │ ├── Cargo.toml +│ │ ├── tauri.conf.json +│ │ └── binaries/ # 嵌入式 FFmpeg +│ ├── package.json +│ ├── vite.config.ts +│ ├── tsconfig.json +│ └── eslint.config.js +│ +└── docs/ # 项目文档 + ├── anytocopy-api.md + ├── anytocopy-integration.md + ├── app-update-system.md + ├── database-design.md + ├── kling-api-dev.md + ├── migrate-avatars-to-local.md + ├── qiniu-kodo-python-sdk-guide.md + ├── video-generation-flow.md + └── volcengine-video-caption-api.md +``` + +## 技术栈 + +### 后端 (python-api) + +**⚠️ Python 版本要求: 3.13+** (项目使用 `|` 类型注解语法) + +| 组件 | 技术 | 版本 | 用途 | +|------|------|------|------| +| Python | - | 3.13+ | 运行环境 | +| Web 框架 | FastAPI | 0.116+ | REST API | +| 数据库 | PostgreSQL | 15+ | 用户认证 + 成本统计 + 形象管理 | +| ORM | SQLAlchemy | 2.0 (异步) | 数据模型 | +| 缓存/调度 | Redis + Async Engine | 5.2+ / 自定义 | 异步任务槽位调度 | +| AI SDK | OpenAI / volcengine | 1.58+ / 5.0+ | LLM 调用 | +| 认证 | python-jose + passlib | 3.4+ / 1.7+ | JWT 认证 | +| 对象存储 | qiniu | 7.13+ | 七牛云存储 | +| HTTP 客户端 | httpx + aiohttp | 0.28+ / 3.13+ | 异步 HTTP | +| 包管理/构建 | uv | - | 虚拟环境、依赖锁定、Docker 构建 | + +**后端架构说明**: +- 后端为"轻量云账号 + 全本地业务数据"模式 +- 云端仅存储:用户账户、形象元数据、成本统计 +- 业务数据(项目/脚本/媒体)全部本地存储 +- 任务调度使用**自定义 Async Engine**(基于 Redis 的槽位管理),**非 Celery** + +### 前端 (tauri-app) + +| 组件 | 技术 | 版本 | 用途 | +|------|------|------|------| +| 桌面框架 | Tauri | 2.x | 桌面应用壳 | +| UI 框架 | React | 19.1+ | 用户界面 | +| 路由 | React Router DOM | 7.x | 页面路由(主壳使用 NavigationContext) | +| 状态管理 | Zustand | 5.x | 全局状态 + Immer 中间件 | +| 数据获取 | SWR | 2.x | 请求缓存 | +| 虚拟列表 | @tanstack/react-virtual | 3.x | 大数据列表渲染 | +| 构建工具 | Vite | 7.x | 构建、开发服务器 | +| 测试 | Vitest + @testing-library | 4.x | 单元测试 | +| 类型生成 | openapi-typescript | 7.x | 从 OpenAPI 生成 TS 类型 | + +### Rust 后端 (src-tauri/src) + +| 模块 | 用途 | +|------|------| +| lib.rs | Tauri 应用入口,命令注册 | +| ffmpeg_cmd.rs | FFmpeg 命令封装(首帧提取、字幕压制、封面合成) | +| video_processing.rs | 视频合成业务逻辑 | +| storage/engine.rs | 本地存储引擎(原子写入、文件锁、路径净化) | +| storage/paths.rs | 集中化路径计算 | +| commands/project.rs | 项目本地存储 IPC 命令 | +| commands/asset.rs | 资源文件保存 IPC 命令 | +| commands/auth_state.rs | 认证状态文件持久化 | +| api_proxy.rs | Python API 代理转发 | +| avatar_cache.rs | 头像视频缓存管理 | + +## 开发环境搭建 + +### 1. 启动 Python 后端 + +```bash +cd python-api + +# 方式一:Docker Compose(推荐) +cp .env.example .env +docker-compose up -d + +# 方式二:本地开发(若 Docker 不可用) +# 启动 PostgreSQL 和 Redis +docker-compose up -d db redis + +# 安装依赖(使用 uv) +uv pip install -e ".[dev]" + +# 启动开发服务器(注意:Docker API 会占用 8080 端口) +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 + +# 另开终端启动 Async Engine Scheduler(必须同时启动,否则任务不会执行) +python -m app.scheduler.main +``` + +后端服务地址: +- API: http://localhost:8080/api/v1 +- 文档: http://localhost:8080/docs +- 健康检查: http://localhost:8080/health + +**Docker Compose 服务组成**(4 个服务): +- `db`: PostgreSQL 15 +- `redis`: Redis 7 +- `api`: FastAPI 开发服务器(端口 8080→8000) +- `scheduler`: Async Engine 统一调度器,处理所有第三方异步任务 + +### 2. 启动 Tauri 前端 + +```bash +cd tauri-app + +# 安装依赖 +npm install + +# 开发模式(自动启动 Vite + Tauri) +npm run tauri dev +``` + +前端窗口: +- Vite 开发服务器: http://localhost:1420 +- 应用窗口: 1440×960(最小 960×640,可调整大小) + +## 构建命令 + +### Python 后端 + +```bash +cd python-api + +# 使用 Makefile(推荐) +make dev # 安装开发依赖 + pre-commit 钩子 +make lint # ruff + mypy +make format # black + ruff --fix +make test # pytest +make test-cov # 覆盖率报告 +make security # bandit + pip-audit +make lint-semantic # 语义层禁词检查 +make ci # 运行所有 CI 检查(format-check + lint + lint-semantic + test + security) +make docker-run # Docker Compose 启动全部服务 +make scheduler # 启动 Async Engine Scheduler + +# 手动命令 +black app/ +ruff check app/ +mypy app/ +bandit -c pyproject.toml -r app/ +pip-audit +pytest +pytest --cov=app + +# 导出 OpenAPI 文档到前端 +python3 -c " +import logging +logging.disable(logging.WARNING) +from app.main import app +import json +print(json.dumps(app.openapi(), indent=2, ensure_ascii=False)) +" > ../tauri-app/src/api/generated/openapi.json + +# Docker 构建 +docker build -t meijiaka-api . +``` + +### Tauri 前端 + +```bash +cd tauri-app + +# 开发 +npm run dev # 纯 Vite 开发(不启动 Tauri) +npm run tauri dev # 完整 Tauri 开发模式 + +# 构建 +npm run build # 前端生产构建 +npm run tauri build # 打包桌面应用 + +# 测试 +npm run test # 运行 Vitest +npm run test:ui # UI 模式 +npm run test:coverage # 覆盖率报告 + +# 代码质量 +npm run lint # ESLint 检查 +npm run lint:fix # ESLint 自动修复 +npm run format # Prettier 格式化 +npm run format:check # Prettier 格式检查 +npm run stylelint # CSS 检查 +npm run stylelint:fix # CSS 自动修复 + +# 类型生成 +npm run gen:api # 从 OpenAPI 生成 TypeScript 类型 +``` + +## 架构说明 + +### 混合路由架构 + +前端 API 调用采用 **智能路由** 策略: + +1. **HTTP 直连 Python**: 纯数据 API(脚本生成、模型管理、任务轮询等) +2. **Tauri IPC → Rust**: 需要本地能力的 API(FFmpeg、文件系统) + +路由决策在 `tauri-app/src/api/client.ts` 中实现。HTTP 客户端会自动处理 `camelCase` ↔ `snake_case` 字段名转换。需要走 Rust IPC 的 API 包括: +- `video_composite_synthesis` // FFmpeg 视频合成 +- `burn_subtitle` // 字幕压制 +- `extract_video_first_frame` // 首帧提取 +- `generate_cover_image` // 封面生成 +- `save_project_meta*` / `load_project_meta*` // 本地文件系统 +- `save_project_segments*` / `load_project_segments*` +- `save_project_asset` / `get_video_save_path` / `get_image_save_path` +- `save_final_product` +- 头像缓存相关 API + +**添加新 API 流程**: +1. Python 端实现端点 +2. 前端直接调用(默认 HTTP) +3. 仅当需要本地能力时,在 Rust 中添加命令并在 `lib.rs` 注册 + +### AI Provider 架构 + +后端 AI 模块采用 **多 Provider 路由** 设计: + +``` +app/ai/ +├── model_router.py # 模型路由器(自动降级) +├── providers/ +│ ├── base.py # Provider 抽象基类 +│ ├── generic_llm_provider.py # 通用 OpenAI 兼容 Provider +│ ├── volcengine_provider.py # 火山方舟官方 SDK +│ └── klingai_provider.py # KlingAI 数字人 +└── prompts/ # 提示词模板(禁止硬编码) +``` + +支持的 AI 平台: +- **火山方舟** (字节跳动) - 推荐,性价比高 +- **OpenAI** - GPT 系列 +- **文心一言** (百度) +- **通义千问** (阿里云) +- **可灵 AI** (快手) - 视频生成、数字人、形象克隆 + +AI 模型配置位于 `python-api/config/ai_models.yaml`,支持热重载,无需重启服务即可更新模型配置。 + +### Async Engine(异步任务调度) + +**⚠️ 重要:项目不使用 Celery,使用自定义 Async Engine** + +架构: +``` +API (POST /tasks/{type}) → Redis JobRegistry → AsyncEngine tick loop → Handlers +``` + +组件: +- **`AsyncEngine`** (`app/scheduler/engine.py`): 每 ~10s 执行 `tick()`,加载运行中任务,按类型分组,并行分发给 Handler,通过 Pipeline 应用 `StateChange`,清理已完成任务 +- **`JobRegistry`** (`app/scheduler/registry.py`): Redis-based 任务 CRUD,使用 `job:{id}` hash + `scheduler:running_tasks` SET +- **`SlotManager`** (`app/scheduler/slot_manager.py`): Redis Lua 原子脚本实现并发槽位抢占/释放 +- **`JobRecord`** / **`StateChange`** (`app/scheduler/models.py`): 调度器内部类型 + +已注册的 Handler(`app/scheduler/main.py`): + +| Handler | 槽位数 | Redis Key | 用途 | +|---------|--------|-----------|------| +| VideoHandler | 18 | `kling:video_slots` | Kling 视频生成(omni + image2video) | +| ImageHandler | 9 | `kling:image_slots` | Kling 图片生成 | +| ScriptHandler | 10 | `script:slots` | LLM 脚本生成(含 AnyToCopy 视频文案提取) | +| SubtitleHandler | 5 | `volc:subtitle_slots` | 火山引擎字幕/自动对齐 | +| CopyHandler | 5 | `anytocopy:slots` | AnyToCopy 视频文案提取 | +| AvatarHandler | 2 | `kling:avatar_slots` | Kling 形象克隆(状态机: pending→voice_processing→element_pending→element_processing→succeed) | + +### TokenManager(API 认证 Token 管理) + +`app/core/token_manager.py` 提供通用的 API 认证 Token 缓存与自动刷新: + +```python +from app.core.token_manager import JWTTokenStrategy, TokenManager + +class MyProvider: + def __init__(self, access_key: str, secret_key: str): + self._token_strategy = JWTTokenStrategy( + access_key=access_key, + secret_key=secret_key, + expires_in=1800, # 30分钟 + ) + + async def _get_headers(self) -> dict[str, str]: + token_info = await TokenManager.get_instance().get_token(self._token_strategy) + return {"Authorization": f"Bearer {token_info.token}"} +``` + +**特性**: +- Token 缓存(避免重复生成) +- 自动刷新(Token 即将过期时自动刷新) +- 并发安全(双重检查锁定,确保并发请求只生成一次 Token) +- 后台预热(提前 10 分钟刷新,避免请求时等待) +- 支持 JWT、OAuth2 等多种策略 + +### 本地存储引擎(Rust) + +Rust 层实现了 defense-in-depth 的本地存储系统:`src-tauri/src/storage/` + +- **`engine.rs`**: 核心原子操作 + - `sanitize_id()` — 白名单 `[a-zA-Z0-9_-]+`,防御路径遍历 + - `sanitize_filename()` — 提取纯文件名,拒绝目录组件 + - `atomic_write_json()` / `atomic_write_bytes()` — 先写 `.tmp` 再 `rename` 原子替换 + - `with_file_lock()` — 通过 `fs2` 实现独占文件锁 + - `read_json()` — 安全读取,文件不存在返回 `None` +- **`paths.rs`**: 集中路径计算 + - `~/Documents/Meijiaka/projects/{id}/` (meta.json, segments.json, assets/) + - `~/Documents/Meijiaka/products/` + - `{app_config_dir}/auth.json` + - `{app_data_dir}/avatars/` + +**所有本地 JSON 读写必须经过 StorageEngine,禁止在命令处理器中直接调用 `fs::write`**。 + +### 数据库模型 + +后端仅保留 **3 个表**: + +``` +users -- 用户账户信息(mobile, nickname, avatar_url) +model_usage_logs -- 大模型调用记录(token, 成本, 响应时间) +avatars -- 克隆形象元数据(云端备份,前端已迁移至本地 JSON) +``` + +**业务数据本地存储**: +- 项目/脚本/分镜 → 前端本地 JSON 文件(`~/Documents/Meijiaka/projects/`) +- 音频/视频/图片文件 → 本地磁盘 +- 用户配置 → localStorage(少量 UI 状态) + +### 数据流规范 + +``` +用户输入主题 ──→ 后端 AI 生成脚本 ──→ 后端返回分镜列表 ──→ 前端保存到本地 + │ │ + └────────────────── 后端不存储脚本数据 ────────────────────────┘ +``` + +### 本地存储结构 + +``` +~/Documents/Meijiaka/ # 用户文档目录 +├── config.json # 全局配置 +├── projects/ # 项目数据 +│ └── {project_id}/ +│ ├── meta.json # 项目元数据 +│ ├── segments.json # 分镜数据 +│ └── assets/ # 资源文件(封面、成品等) +├── products/ # 成品视频目录 +├── avatars.json # 形象列表(本地) +└── cache/ # 缓存目录 +``` + +项目元数据 `meta.json` 关键字段: +- `id`, `title`, `topic`, `status` (draft | published) +- `currentStep`: 1=脚本生成, 2=形象视频, 3=字幕压制, 4=封面制作, 5=视频合成 +- `createdAt`, `updatedAt`, `exportedAt` +- `coverPath`, `finalVideoPath` +- `selectedElementId`, `selectedHumanId` +- `coverConfig`, `scriptDuration`, `scriptType` + +分镜数据 `segments.json` 字段: +- `id`, `type` (segment | empty_shot), `scene`, `voiceover`, `duration` +- `videoPath`, `videoUrl`, `elementId`, `voiceId` +- `alignmentResult`, `burnedVideoPath`, `burnedAt` + +### 前端导航 + +主应用壳使用 **自定义 NavigationContext**(React Context)实现页面切换,映射 `Record`。`react-router-dom` 已安装但主要用于未来扩展或特定路由场景,当前主流程不使用 BrowserRouter 进行导航。 + +### 状态管理 + +六個專門的 Zustand store: + +| Store | 职责 | 持久化 | +|-------|------|--------| +| `authStore` | JWT、UserInfo、登录/登出 | Tauri `auth.json`(或 localStorage fallback) | +| `projectStore` | 分镜、currentStep、选题、封面配置 | **仅 UI 标志**通过 `persist`;业务数据显式写入本地 JSON | +| `taskStore` | 异步任务状态/进度/消息 | **无**(内存 only,真相源在后端 Redis) | +| `uiStore` | Toast 通知队列 | 无 | +| `progressStore` | 全局进度模态框 | 无 | +| `settingsStore` | 主题模式、用户偏好 | localStorage | + +`projectStore` **不自动保存**。数据在显式过渡点持久化到磁盘(如进入 step 2、调用 `setFinalVideoPath` 时触发 `saveMetaToLocalFile`)。`saveMetaToLocalFile()` 通过 Promise 链串行化写入,避免并发覆盖。 + +## 开发规范 + +### 核心原则 + +1. **后端环境优先使用 Docker Compose**: 开发时通过 `docker-compose up -d` 启动后端。前端默认连接 `http://127.0.0.1:8080/api/v1`。 +2. **接口契约优先**: 后端承诺无论使用什么 AI 模型,输出永远符合同一个 Schema +3. **类型单一来源**: 后端 Schema 是权威,前端通过 OpenAPI 生成类型 +4. **Adapter 层隔离**: 前后端字段差异只允许在 Adapter 层处理 +5. **数据库分层**: API → Service → CRUD → Model,禁止跨层调用 +6. **提示词文件化**: 除前端输入外,后端不允许硬编码任何 Prompt +7. **配置统一管理**: 所有配置通过 `get_settings()` 读取,禁止直接使用 `os.getenv()` +8. **本地存储必须经过 StorageEngine**: Rust 层所有文件操作使用 `atomic_write_json` + `with_file_lock` + +### 配置管理规范 + +**架构层级:** +``` +.env (Layer 1) ──→ Settings (Layer 2) ──→ 服务层 (Layer 3) + ↑ + 唯一配置出口 +``` + +**强制规范:** +- **所有服务**必须使用 `from app.config import get_settings` 读取配置 +- **禁止**在服务层使用 `os.getenv()` 或 `os.environ.get()` +- **所有配置项**必须在 `app/config.py` 的 `Settings` 类中定义 +- **敏感信息**(API Keys、Secrets)必须通过环境变量注入 +- **业务默认值**可以硬编码在 `Settings` 中 + +**添加新配置流程:** +1. 在 `app/config.py` 的 `Settings` 类中添加字段定义 +2. 在 `.env` 中添加实际值(敏感信息)或使用默认值 +3. 在服务层通过 `get_settings()` 读取 +4. 更新 `.env.example` 文档 + +### 语义层防护网 + +项目强制执行语义分层,禁止供应商术语泄漏到业务层: + +| 层级 | 职责 | 禁词示例 | +|------|------|----------| +| Layer 6 (Presentation) | API Schema | `element_id`, `kling_task_id` | +| Layer 4 (Orchestration) | Scheduler | `task_id`(应使用 `job_id`) | +| Layer 3 (Domain) | Service | 供应商特定术语 | +| Layer 2 (Adapter) | Provider | 允许使用供应商原生术语 | + +Makefile 提供 `make lint-semantic` 进行自动化检查: +- API 层(除 `klingai.py`)禁止使用 `element_id`(应使用 `provider_element_id` 或 `human_id`) +- Scheduler 层禁止使用 `task_id`(应使用 `job_id`) +- 全局禁止 `kling_task_id`(应使用 `provider_task_id`) +- Scheduler Redis key 必须使用 `job:` 而非 `task:` + +### 快速参考 + +| 场景 | 正确做法 | +|------|---------| +| 后端换 AI 模型 | 修改 `services/ai_response_utils.py` 标准化层,不修改 Schema | +| 后端新增字段 | `Optional[T] = Field(None)`,向后兼容 | +| 后端修改字段 | 保留旧字段,标记 deprecated,逐步迁移 | +| 前端需要新字段 | Store 中 `extends` 基础类型 | +| 数据清洗 | **只在** Adapter 层,禁止在组件层 | +| 新增数据库实体 | 创建 Model → CRUD → API(分层开发)| +| 数据库查询 | 在 CRUD 层封装,API 层调用 | +| 事务管理 | API 层控制,通过 `get_db` 依赖注入 | +| 新增提示词 | 创建 `.txt` 文件,使用 `_load_prompt()` 加载 | +| 新增本地文件操作 | 使用 `storage::engine` 原子写入 + 文件锁 | + +### 后端分层架构 + +``` +API Layer (api/v1/*.py) + ↓ 调用 +Service Layer (services/*.py) - 可选,复杂业务 + ↓ 调用 +CRUD Layer (crud/*.py) + ↓ 调用 +Model Layer (models/*.py) + ↓ 调用 +Database Layer (db/*.py) +``` + +**禁止**: +- API 层直接操作 Model +- CRUD 层返回 Schema(应返回 Model) +- Service 层直接操作数据库(应通过 CRUD) +- 在业务代码中写 SQL + +### 前端类型规范 + +| 层级 | 类型来源 | 说明 | +|------|----------|------| +| 后端 Schema | `python-api/app/schemas/*.py` | Pydantic 模型,OpenAPI 生成源 | +| 前端基础类型 | `tauri-app/src/api/types.ts` | 手写的核心类型,与后端对齐 | +| 前端完整类型 | `tauri-app/src/api/generated/schema.ts` | OpenAPI 自动生成,只读 | +| Store 扩展 | `tauri-app/src/store/*.ts` | `extends` 基础类型添加前端字段 | + +### 间距规范 + +前端使用基于 4px 的网格系统,定义在 `tauri-app/src/styles/variables.css`: + +| 变量 | 值 | 使用场景 | +|------|-----|----------| +| `--spacing-2xs` | 2px | 微调控件、边框线 | +| `--spacing-xs` | 4px | 紧凑间隙、图标边距 | +| `--spacing-sm` | 8px | 小间隙、按钮内边距-y | +| `--spacing-md` | 12px | 标准间隙、卡片内边距 | +| `--spacing-lg` | 16px | 大间隙、区块间距 | +| `--spacing-xl` | 24px | 页面区块、内容分隔 | +| `--spacing-2xl` | 32px | 大区块间距、页面边距 | +| `--spacing-3xl` | 48px | 页面级间距、Hero 区域 | + +## 代码风格 + +### Python + +- **格式化**: Black (line-length: 100, target-version: py313) +- **检查**: Ruff (E, F, I, N, W, UP, B, C4, SIM) +- **类型**: MyPy(非严格模式全局,但 `app.schemas.*`、`app.crud.*`、`app.scheduler.handlers.*` 强制严格模式) +- **文档**: 中文注释,Google Style Docstrings +- **安全**: Bandit + pip-audit +- **Git Hooks**: pre-commit(Black、Ruff、uv lock 同步检查) + +### TypeScript/React + +- **类型**: 严格 TypeScript 模式(`strict: true`, `noUnusedLocals: true`, `noUnusedParameters: true`) +- **组件**: 函数组件 + Hooks +- **状态**: Zustand 管理全局状态(配合 Immer 处理不可变更新) +- **样式**: 普通 CSS + CSS 变量(`tauri-app/src/styles/variables.css`) +- **ESLint**: 使用 `eslint.config.js`(Flat Config),含 React Hooks 和 React Refresh 规则 +- **Prettier**: semi=true, singleQuote=true, tabWidth=2, printWidth=100 +- **Stylelint**: `stylelint-config-standard`,禁止 magic px 用于 `border-radius` 和 `font-size` + +### Rust + +- **格式化**: rustfmt +- **检查**: cargo clippy +- **注释**: 中文文档注释 + +### 提交规范 + +``` +feat: 新功能 +fix: 修复 +docs: 文档 +refactor: 重构 +test: 测试 +chore: 构建/工具 +``` + +## 测试策略 + +### 后端测试 + +```bash +cd python-api + +# 运行所有测试 +pytest -v + +# 覆盖率报告 +pytest --cov=app --cov-report=html --cov-report=term +``` + +**测试配置** (`pyproject.toml`): +- asyncio_mode = "auto" +- 测试文件命名: `test_*.py` + +> **注**:当前项目中 `python-api/tests/` 目录尚未创建,后端测试待补充。 + +### 前端测试 + +```bash +cd tauri-app + +# 运行 Vitest +npm run test + +# UI 模式 +npm run test:ui + +# 覆盖率报告 +npm run test:coverage +``` + +**测试配置**: +- 测试框架: Vitest 4.x + @testing-library/react + jsdom +- 测试文件: `src/**/*.test.ts(x)` +- Mock 配置: `src/__tests__/setup.ts` +- 自动 Mock: localStorage, Tauri API (`@tauri-apps/api/core`) +- 示例测试: `src/store/__tests__/authStore.test.tsx` + +## 安全注意事项 + +1. **SECRET_KEY**: 生产环境必须修改为强随机密钥(`get_settings()` 会在生产环境校验) +2. **CORS**: 生产环境限制为实际前端域名,开发环境 `DEBUG=true` 时允许所有来源 +3. **API Keys**: 不要提交到 Git,使用 `.env` 文件注入 +4. **FFmpeg**: 嵌入的二进制文件需验证来源 +5. **文件上传**: 限制文件类型和大小,防止攻击 +6. **路径遍历**: Rust StorageEngine 的 `sanitize_id()` 和 `sanitize_filename()` 防御路径遍历攻击 +7. **原子写入**: 所有本地 JSON 使用 `atomic_write_json`(先写 `.tmp` 再 `rename`) +8. **文件锁**: 并发 RMW 操作使用 `with_file_lock` 防止竞态 +9. **日志**: 后端日志写入 `~/Documents/Meijiaka/logs/api_YYYYMMDD.log` + +## 配置说明 + +### Python 后端 (.env) + +关键环境变量: + +```bash +# 数据库 (PostgreSQL) +DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/meijiaka + +# Redis +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_DB=0 + +# JWT 密钥(生产环境必须修改) +SECRET_KEY=your-secret-key-here-change-in-production +ACCESS_TOKEN_EXPIRE_MINUTES=10080 + +# AI API Keys +VOLCENGINE_API_KEY=your-volcengine-key +VOLCENGINE_CAPTION_APPID=your-caption-appid +VOLCENGINE_CAPTION_TOKEN=your-caption-token +KLINGAI_ACCESS_KEY=your-kling-access-key +KLINGAI_SECRET_KEY=your-kling-secret-key +OPENAI_API_KEY=sk-your-openai-key + +# 七牛云存储 +QINIU_ACCESS_KEY=your-qiniu-access-key +QINIU_SECRET_KEY=your-qiniu-secret-key +QINIU_VIDEO_BUCKET=media-liche +QINIU_IMAGE_BUCKET=img-liche + +# CORS 允许的前端地址 +CORS_ORIGINS=http://localhost:1420,http://127.0.0.1:1420,http://localhost:8080 +``` + +### Tauri 配置 (tauri.conf.json) + +```json +{ + "productName": "美家卡智影", + "identifier": "cn.meijiaka.ai-video", + "build": { + "devUrl": "http://localhost:1420", + "frontendDist": "../dist" + }, + "bundle": { + "externalBin": ["binaries/ffmpeg"], + "resources": { + "fonts/*": "fonts/" + } + } +} +``` + +### AI 模型配置 (config/ai_models.yaml) + +模型配置文件支持热重载,无需重启服务即可更新模型配置。主要配置项: + +- **platforms**: AI 平台配置(mock, volcengine, klingai) +- **models**: 可用模型列表及其能力标签 [script, polish, chat, image, embedding, vision] +- **task_defaults**: 任务类型到模型的默认映射 + +## 视频创作流程 + +1. **脚本生成** (Step 1) - AI 生成视频脚本和分镜 +2. **形象视频** (Step 2) - 选择数字人形象,生成视频片段 +3. **字幕压制** (Step 3) - 生成字幕并压制到视频中 +4. **封面制作** (Step 4) - 生成视频封面 +5. **视频合成** (Step 5) - FFmpeg 拼接视频片段,导出最终视频 + +## 常见问题 + +### Q: 火山方舟如何配置? + +1. 注册火山引擎账号并实名认证 +2. 创建 API Key +3. 开通模型并创建推理接入点 +4. 在 `.env` 中设置 `VOLCENGINE_API_KEY` + +### Q: 可灵 AI 如何配置? + +1. 前往可灵 AI 开发者平台 https://klingai.com/document-api +2. 获取 Access Key 和 Secret Key +3. 在 `.env` 中设置 `KLINGAI_ACCESS_KEY` 和 `KLINGAI_SECRET_KEY` + +### Q: FFmpeg 在哪里? + +Tauri 应用已嵌入 FFmpeg 二进制文件: +- 位置: `tauri-app/src-tauri/binaries/ffmpeg-*` +- 使用: Rust 层通过 `ffmpeg_cmd` 模块调用 +- 打包时会作为 `externalBin` 资源嵌入 + +### Q: 后端换了 AI 模型,输出格式变了怎么办? + +修改 `services/ai_response_utils.py` 中的标准化函数,增加新的字段映射,**不要**修改 API Schema。 + +### Q: 如何新增/修改提示词? + +1. 创建文件: `app/ai/prompts/my_prompt.txt` +2. 加载使用: `prompt = self._load_prompt("my_prompt")` +3. **禁止**: 在 Python 代码中直接写 `"""你是一位..."""` + +### Q: 项目数据是如何持久化的? + +- 项目元数据(`meta.json`)和分镜数据(`segments.json`)保存在 `~/Documents/Meijiaka/projects/{project_id}/` +- 不通过 Zustand `persist` 保存项目数据,而是通过 `localProjectApi` 显式调用 Tauri IPC 写入文件 +- `projectStore` 的 `persist` 中间件仅保存少量 UI 状态 + +### Q: Async Engine 和 Celery 有什么区别? + +本项目使用**自定义 Async Engine** 替代 Celery: +- 基于 Redis 的槽位管理(SlotManager),限制各类型任务的并发数 +- 独立的 `scheduler` 进程(`python -m app.scheduler.main`) +- 每个 Handler 实现 `AsyncHandler` 接口,状态机驱动任务生命周期 +- 优势:更细粒度的并发控制、统一状态机、无 Celery 依赖 + +--- + +**最后更新**: 2026-04-17 +**架构模式**: 单机版(轻量云账号 + 全本地业务数据) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..673983d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,518 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## 项目概述 + +美家卡智影 (Meijiaka AI Video) - AI 视频创作平台。一个 AI 驱动的桌面应用,采用 **Tauri + React + FastAPI** 混合架构,用户可以通过 AI 生成脚本、创建数字人视频,自动生成字幕,最终本地合成完整的营销视频。 + +### 环境要求 + +| 组件 | 版本要求 | +|------|----------| +| Python | **3.13+** (代码使用 `|` 类型注解语法) | +| Node.js | 20+ | +| Rust | 1.70+ | +| Docker | 20+ (可选,用于数据库) | + +核心设计理念:**轻量云账号 + 全本地业务数据** - 云端只存储用户认证和使用日志,所有项目/脚本/媒体都存在用户本地。 + +## 架构 + +### 混合架构 + +- **FastAPI 后端**: 处理 AI 模型调用、用户认证、API 服务 +- **Tauri + React 前端**: 桌面 UI,React 负责渲染,Tauri 提供系统能力 +- **Rust 后端**: 通过 Tauri IPC 处理本地操作(FFmpeg 视频处理、文件系统访问) + +### 存储策略 + +核心设计理念:**轻量云账号 + 全本地业务数据** - 云端只存储用户认证和使用日志,所有项目/脚本/媒体都存在用户本地。 + +- **云端**: PostgreSQL 只存储 2 张表:`users` (用户账户)、`model_usage_logs` (用量统计) + - `avatars` 表已废弃:数字人名片元数据现在纯本地存储 `avatars.json` +- **本地**: JSON 文件存储项目/脚本/分镜数据、数字人元数据,用户磁盘存储媒体文件,FFmpeg 处理视频合成 +- **缓存/队列**: Redis + Async Engine Scheduler 处理异步任务 + +### 混合通信模式 + +| 通信模式 | 使用场景 | 前端调用方式 | +|---------|---------|------------| +| HTTP → FastAPI | AI 生成、认证、配置管理 | `client.get/post/put/delete()` | +| Tauri IPC → Rust | FFmpeg 视频处理、本地文件系统 | `ipc.request()` 或直接 `invoke()` | + +**通信模块**: +- `tauri-app/src/api/client.ts` - HTTP 客户端,自动处理 camelCase/snake_case 转换 +- `tauri-app/src/api/ipc.ts` - IPC 客户端 +- `tauri-app/src/api/modules/localStorage.ts` - 本地项目存储(走 IPC) +- `tauri-app/src/api/modules/videoComposite.ts` - 视频合成(走 IPC) + +### AI Provider 架构 + +后端 AI 模块采用多 Provider 设计: +- `app/ai/model_router.py` - 模型路由器,支持自动降级 +- `app/ai/providers/base.py` - 抽象基类 +- `app/ai/providers/*` - 具体实现(OpenAI、火山引擎、KlingAI 等) +- `app/ai/prompts/` - 提示词模板文件 + +支持的 AI 平台:火山方舟(推荐)、OpenAI、百度文心一言、阿里云通义千问、KlingAI(数字人视频生成)。 + +模型配置文件:`python-api/config/ai_models.yaml`(支持热重载) + +### Token 管理 + +外部 API 认证 Token 使用 `app/core/token_manager.py` 统一管理: +- Token 缓存(避免重复生成) +- 自动刷新(Token 即将过期时自动刷新) +- 并发安全(双重检查锁定) +- 支持 JWT、OAuth2 等多种策略 + +### 数据流 + +1. **脚本生成**: 用户输入 → FastAPI AI 代理 → 标准化输出 → 前端保存到本地 JSON +2. **数字人视频**: 后端调用 KlingAI API → 返回视频 URL → 前端下载并本地存储 +3. **视频合成**: 前端 → Tauri IPC → Rust 后端 → FFmpeg → 渲染最终视频文件 + +### 本地存储结构(用户机器) + +``` +~/Documents/Meijiaka/ +├── config.json # 全局应用配置 +├── projects/ +│ └── {project_id}/ +│ ├── meta.json # 项目元数据 +│ ├── segments.json # 脚本/分镜数据 +│ └── assets/ # 媒体文件 +├── avatars/ +│ └── {avatar_id}/ +│ ├── meta.json # 数字人名片配置 +│ └── source.mp4 # 源视频 +└── cache/ # 临时文件 +``` + +## 目录结构 + +``` +ai-meijiaka/ +├── python-api/ # FastAPI 后端服务 +│ ├── app/ +│ │ ├── api/v1/ # REST API 端点 +│ │ ├── ai/ # AI 模型路由和 Provider +│ │ ├── ai/prompts/ # 提示词模板文件 +│ │ ├── core/ # 安全、配置、异常处理 +│ │ ├── db/ # 数据库配置 +│ │ ├── models/ # SQLAlchemy 数据模型 +│ │ ├── schemas/ # Pydantic 验证模型 +│ │ ├── services/ # 业务逻辑和 AI 服务代理 +│ │ ├── scheduler/ # Async Engine 统一异步调度器 +│ │ ├── config.py # 配置管理 +│ │ └── main.py # 应用入口 +│ ├── config/ # AI 模型配置(YAML) +│ ├── tests/ # pytest 测试套件 +│ ├── scripts/ # 管理和测试脚本 +│ └── docker-compose.yml # Docker 服务编排 +│ +├── tauri-app/ # Tauri 桌面应用 +│ ├── src/ # React 前端源码 +│ │ ├── api/ # API 客户端和类型 +│ │ │ ├── adapters/ # 前后端字段差异适配 +│ │ │ ├── generated/ # OpenAPI 自动生成类型 +│ │ │ └── modules/ # API 模块封装 +│ │ ├── components/ # 可复用 React 组件 +│ │ ├── pages/ # 页面组件(路由) +│ │ ├── store/ # Zustand 全局状态管理 +│ │ ├── hooks/ # 自定义 React Hooks +│ │ └── utils/ # 前端工具函数 +│ ├── src-tauri/ # Rust 后端 +│ │ ├── src/ +│ │ │ ├── lib.rs # Tauri 应用入口,命令注册 +│ │ │ ├── commands/ # 按领域拆分的命令模块 +│ │ │ │ ├── asset.rs # 资源文件操作 +│ │ │ │ ├── auth_state.rs # 认证状态管理 +│ │ │ │ ├── avatar.rs # 数字人头像管理 +│ │ │ │ ├── product.rs # 产品相关 +│ │ │ │ └── project.rs # 项目存储操作 +│ │ │ ├── storage/ # 存储引擎分层 +│ │ │ │ ├── mod.rs # 模块导出 +│ │ │ │ ├── paths.rs # 路径计算 +│ │ │ │ ├── engine.rs # 核心存储引擎(原子写+文件锁) +│ │ │ │ ├── auth.rs # 认证存储 +│ │ │ │ ├── project.rs # 项目存储 +│ │ │ │ ├── avatar.rs # 头像存储 +│ │ │ │ └── cache.rs # 缓存存储 +│ │ │ ├── ffmpeg_cmd.rs # FFmpeg 命令封装 +│ │ │ ├── video_processing.rs # 视频合成逻辑 +│ │ │ ├── api_proxy.rs # Python API 代理 +│ │ │ ├── avatar_cache.rs # 头像视频缓存管理 +│ │ │ └── utils.rs # 通用工具函数 +│ │ ├── binaries/ # 嵌入的 FFmpeg 可执行文件 +│ │ └── Cargo.toml # Rust 依赖配置 +│ └── package.json # NPM 依赖和脚本 +│ +└── docs/ # 开发文档 +``` + +## 常用命令 + +### 后端 (python-api) + +项目使用 `uv` 进行依赖管理,并提供了 `Makefile` 封装常用命令: + +```bash +cd python-api + +# 使用 uv 和 Makefile(推荐) +make dev # 安装开发依赖并配置 pre-commit +make docker-run # 使用 Docker Compose 启动所有服务(db, redis, api, scheduler) +make run # 启动 FastAPI 开发服务器 +make scheduler # 启动 Async Engine Scheduler +make lint # 运行代码检查 (ruff + mypy) +make format # 格式化代码 +make test # 运行所有测试 +make security # 运行安全扫描 (bandit + pip-audit) + +# 手动方式 +# 安装依赖 +python -m venv venv && source venv/bin/activate +pip install -e ".[dev]" + +# 启动 PostgreSQL + Redis(必需) +docker-compose up -d db redis + +# 启动 FastAPI 开发服务器 +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 + +# 启动 Async Engine Scheduler(另开终端) +python -m app.scheduler.main + +# 代码质量 +black app/ # 格式化代码(行宽 100) +ruff check app/ # 代码检查 +mypy app/ # 严格类型检查 +bandit -c pyproject.toml -r app/ # 安全扫描 +pip-audit # 依赖漏洞检测 +python scripts/check_config_architecture.py # 检查配置架构一致性 + +# 导出 OpenAPI 文档到前端 +python3 -c " +import logging +logging.disable(logging.WARNING) +from app.main import app +import json +print(json.dumps(app.openapi(), indent=2, ensure_ascii=False)) +" > ../tauri-app/src/api/generated/openapi.json + +# 测试 +pytest # 运行所有测试 +pytest tests/test_script.py -v # 运行单个测试文件 +pytest --cov=app # 覆盖率报告 + +# Docker +docker-compose up -d # 启动所有服务(db, redis, api, scheduler) + +# 端口占用检查 +lsof -i :8080 # 检查 8080 端口占用 +``` + +**可用 Makefile 命令:** + +| 命令 | 用途 | +|------|------| +| `make help` | 显示帮助信息 | +| `make install` | 安装生产依赖(使用 lock 文件)| +| `make dev` | 安装开发依赖并配置 pre-commit | +| `make update-lock` | 更新 requirements.lock | +| `make lint` | 运行代码检查 (ruff + mypy) | +| `make format` | 格式化代码 (black + ruff) | +| `make format-check` | 检查代码格式(不修改)| +| `make test` | 运行测试 | +| `make test-cov` | 运行测试并生成覆盖率报告 | +| `make security` | 运行安全扫描 | +| `make run` | 启动开发服务器 | +| `make scheduler` | 启动 Async Engine Scheduler | +| `make docker-run` | Docker Compose 启动全部服务 | +| `make docker-down` | 停止 Docker 服务 | +| `make clean` | 清理缓存文件 | +| `make ci` | 运行所有 CI 检查 | + +### 前端 (tauri-app) + +```bash +cd tauri-app + +# 安装依赖 +npm install + +# 开发 +npm run dev # 仅启动 Vite(不打开 Tauri 窗口) +npm run tauri dev # 完整 Tauri 桌面开发模式 + +# 构建 +npm run build # 前端生产构建 +npm run tauri build # 打包桌面应用(.dmg/.exe/.AppImage) + +# 代码质量 +npm run lint # ESLint 检查 JS/TS +npm run lint:fix # ESLint 自动修复 +npm run format # Prettier 格式化代码 +npm run stylelint # CSS 检查 + +# 测试 +npm run test # 运行 Vitest +npm run test:coverage # 覆盖率报告 +npm run test:ui # 打开 Vitest UI + +# 类型生成 +npm run gen:api # 从 OpenAPI schema 生成 TypeScript 类型 +``` + +### 数据库迁移 + +项目使用 Alembic 进行数据库迁移: + +```bash +cd python-api + +# 生成新迁移(修改模型后) +alembic revision --autogenerate -m "description" + +# 应用迁移 +alembic upgrade head + +# 回滚迁移 +alembic downgrade -1 +``` + +### 开发提示 + +- **Tauri 调试**: 使用 `npm run tauri dev` 时,Rust 后端日志在终端输出,前端日志在浏览器控制台 +- **本地项目路径**: 项目数据保存在 `~/Documents/Meijiaka/projects/{project_id}/` +- **配置修改**: AI 模型配置 `python-api/config/ai_models.yaml` 支持热重载,无需重启服务 +- **类型同步**: 修改后端 API 后,记得重新导出 OpenAPI 并运行 `npm run gen:api` +- **Async Engine Scheduler**: 系统使用 Slot-Based Scheduler 统一调度所有第三方异步任务: + - `video` - 数字人视频生成(18 slots) + - `avatar_clone` - 形象克隆(2 slots) + - `image` - 图片生成(9 slots) + - `subtitle` - 字幕生成(5 slots) + - `copy` - 文案提取(5 slots) +- **任务状态**: 任务状态唯一真相源为后端 Redis,`taskStore` 不持久化,启动时从后端 `GET /tasks` 查询 +- **项目数据**: 项目元数据和分镜数据通过 IPC 显式写入本地文件,不通过 Zustand persist 持久化 +- **字幕渲染**: 使用 `assjs` 库进行 ASS/SSA 字幕预览渲染,WASM 和 Worker 文件通过 Vite 插件复制到 `public/` 目录,修改资源路径后需要检查插件配置 + +## 开发规范 + +### 后端 (Python) + +- **格式化**: Black (行宽: 100) +- **检查**: Ruff +- **类型**: MyPy (strict 模式) +- **架构**: API → Service → CRUD → Model,禁止跨层调用 +- **数据库**: 始终使用异步 SQLAlchemy,事务在 API 层控制 +- **AI 集成**: 无论使用什么提供者,输出 Schema 必须保持一致,在 Service 层标准化 +- **提示词**: 所有提示词放在 `app/ai/prompts/` 单独文件,不硬编码 +- **配置管理**: 所有配置通过 `from app.config import get_settings` 读取,禁止直接使用 `os.getenv()`,所有配置项必须在 `Settings` 类中定义 + +### 配置管理强制规范 + +**架构层级:** +``` +.env (Layer 1) ──→ Settings (Layer 2) ──→ 服务层 (Layer 3) + ↑ + 唯一配置出口 +``` + +**强制规则:** +- **所有服务**必须使用 `from app.config import get_settings` 读取配置 +- **禁止**在服务层、API 层直接使用 `os.getenv()` 或 `os.environ.get()` +- **所有配置项**必须在 `app/config.py` 的 `Settings` 类中定义 +- **敏感信息**(API Keys、Secrets)必须通过环境变量注入 +- **业务默认值**可以硬编码在 `Settings` 中 + +**添加新配置流程:** +1. 在 `app/config.py` 的 `Settings` 类中添加字段定义 +2. 使用 `Field(default=..., description="...")` 提供默认值和说明 +3. 敏感信息使用 `str | None = None` 类型 +4. 更新 `.env.example` 文档 + +### Rust (Tauri 后端) + +- **格式化**: `rustfmt`(默认配置) +- **检查**: `cargo clippy`(零警告) +- **模块组织**: 命令按领域拆分到 `src/commands/{domain}.rs`,在 `lib.rs` 中注册 +- **存储分层**: 存储逻辑按领域拆分到 `src/storage/{domain}.rs` +- **命令参数**: Tauri IPC 命令必须使用 Args 结构体接收参数: + ```rust + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SaveProjectMetaArgs { + pub project_id: String, + pub data: serde_json::Value, + } + ``` +- **禁止**: 命令函数直接使用 camelCase 参数名(会产生 `non_snake_case` 警告) + +### 本地数据存储规范(Tauri/Rust) + +**分层架构:** +``` +Layer 1: 页面组件(Pages/Components) — 只操作 Store,禁止直接调用 IPC save +Layer 2: Zustand Store(内存状态) — Immer 不可变更新 +Layer 3: PersistManager(持久化协调) — debounce 批量、flush 强制、错误上报 +Layer 4: API 模块(localStorageApi 等) — 类型安全的 IPC 调用封装 +Layer 5: Rust StorageEngine(文件系统) — sanitize + atomic_write + file_lock +``` + +**强制规范:** +1. **禁止页面组件直接调用 `localProjectApi.saveXxx()`** — 必须通过 Store → PersistManager +2. **禁止 Rust 命令函数直接 `fs::write`** — 必须通过 `StorageEngine::atomic_write_json` +3. **所有 ID 参数必须 `sanitize_id`** — 路径参数白名单校验(`[a-zA-Z0-9_-]+`) +4. **所有 JSON 写操作必须原子化** — 临时文件 + `fs::rename` +5. **RMW 操作必须加锁** — `with_file_lock` 或 Mutex + +**StorageEngine 核心能力:** +- `sanitize_id(id)` — ID 白名单校验,防御路径遍历 +- `sanitize_filename(name)` — 提取纯文件名,拒绝目录组件 +- `atomic_write_json(path, value)` — 先写 `.tmp` 再 rename,防崩溃截断 +- `with_file_lock(path, f)` — 文件锁保护 RMW 操作 +- `read_json(path)` — 安全读取,文件不存在返回 `None`,损坏返回 `Err` + +### 前端 (TypeScript/React) + +- **类型**: 严格 TypeScript 模式 +- **组件**: 函数组件 + Hooks +- **状态管理**: Zustand 管理全局状态,Immer 处理不可变更新 +- **数据获取**: SWR 缓存,自动 localStorage 降级 +- **API 客户端**: 从后端 OpenAPI schema 自动生成类型 +- **命名风格**: camelCase(自动与后端 snake_case 转换) +- **本地存储**: 项目数据通过 Tauri IPC 保存到 `~/Documents/Meijiaka/projects/` + +### 提交规范 + +``` +feat: 新功能 +fix: 修复 +docs: 文档 +refactor: 重构 +test: 测试 +chore: 构建/工具 +``` + +## 环境配置 + +### 后端 (.env) + +```bash +# 数据库 +DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/meijiaka +REDIS_URL=redis://localhost:6379/0 + +# JWT 认证 +SECRET_KEY=your-secret-key-here +ACCESS_TOKEN_EXPIRE_MINUTES=10080 + +# AI 服务凭证 +VOLCENGINE_API_KEY=your-volcengine-key +VOLCENGINE_CAPTION_APPID=your-caption-appid +VOLCENGINE_CAPTION_TOKEN=your-caption-token +OPENAI_API_KEY=sk-your-openai-key +KLINGAI_ACCESS_KEY=your-kling-access-key +KLINGAI_SECRET_KEY=your-kling-secret-key + +# 七牛云存储(数字人视频持久化) +QINIU_ACCESS_KEY=your-qiniu-access-key +QINIU_SECRET_KEY=your-qiniu-secret-key +QINIU_VIDEO_BUCKET=media-bucket +QINIU_IMAGE_BUCKET=image-bucket + +# CORS 配置 +CORS_ORIGINS=http://localhost:1420,http://127.0.0.1:1420,http://localhost:8080 +``` + +## 服务地址 + +- API: http://localhost:8080/api/v1 +- 文档: http://localhost:8080/docs +- Vite 开发服务器: http://localhost:1420 + +## 关键开发文件 + +| 文件 | 用途 | +|------|------| +| `python-api/app/main.py` | FastAPI 应用入口 | +| `python-api/app/api/v1/*.py` | API 端点定义 | +| `python-api/app/ai/model_router.py` | AI 模型路由和降级 | +| `python-api/app/services/*.py` | 业务逻辑和 AI 响应标准化 | +| `python-api/config/ai_models.yaml` | AI 模型配置 | +| `tauri-app/src/App.tsx` | 主 React 组件 | +| `tauri-app/src/api/client.ts` | 智能路由的 API 客户端 | +| `tauri-app/src/store/projectStore.ts` | 项目状态管理 | +| `tauri-app/src-tauri/src/lib.rs` | Rust 命令注册 | +| `tauri-app/src-tauri/src/commands/project.rs` | 项目存储 IPC 命令 | +| `tauri-app/src-tauri/src/storage/engine.rs` | 核心存储引擎(原子写+校验)| +| `tauri-app/src-tauri/src/video_processing.rs` | FFmpeg 视频合成 | +| `tauri-app/src-tauri/src/avatar_cache.rs` | 头像视频缓存管理 | +| `python-api/app/core/token_manager.py` | API Token 缓存与自动刷新 | +| `python-api/app/config.py` | Pydantic Settings 配置管理 | +| `tauri-app/src/pages/VideoCreation/SubtitleBurning.tsx` | 字幕压制页面(ASS 字幕渲染) | +| `tauri-app/src/hooks/useAssJsRenderer.ts` | assjs 字幕渲染 Hook | +| `tauri-app/src/utils/assGenerator.ts` | ASS 字幕文件生成工具 | + +## 额外开发文档 + +项目 `docs/` 目录包含详细的深度开发文档: + +| 文档 | 主题 | +|------|------| +| `docs/video-generation-flow.md` | 完整视频生成流程说明 | +| `docs/kling-api-dev.md` | KlingAI 数字人视频 API 对接开发文档 | +| `docs/app-update-system.md` | 应用自动更新系统设计 | +| `docs/anytocopy-integration.md` | 版权素材集成说明 | +| `docs/anytocopy-api.md` | 版权素材 API 文档 | +| `docs/volcengine-video-caption-api.md` | 火山引擎字幕 API 对接 | +| `docs/qiniu-kodo-python-sdk-guide.md` | 七牛云存储 SDK 集成指南 | +| `docs/database-design.md` | 数据库设计文档 | +| `docs/unified-async-scheduler.md` | 统一异步调度器设计 | +| `docs/semantic-refactoring-plan.md` | 后端语义重构计划 | +| `docs/migrate-avatars-to-local.md` | 头像数据迁移到本地说明 | + +## 统一术语表(语义治理) + +后端代码已完成语义治理重构,所有开发必须遵守统一术语表,禁止使用废弃别名。 + +整个后端划分为 6 个语义层级,每一层只使用属于该层的术语: + +``` +Layer 6: Presentation (API Schema / 前端适配层) → Segment, Human, Job, Script +Layer 5: Application (API 路由) → Segment, Human, Job, Project +Layer 4: Orchestration (Scheduler / SlotManager) → Job, JobRecord, Slot, Handler +Layer 3: Domain (Service / 业务逻辑) → Segment, Human, VideoComposition, Caption +Layer 2: Adapter (Provider Client) → KlingJob, KlingElement, VolcJob, ProviderTaskId +Layer 1: Infrastructure (DB / Redis / HTTP) → 底层技术术语 +``` + +### 术语对照表 + +| 业务概念 | 官方术语 | 使用层级 | 禁止使用的别名 | +|---------|---------|---------|--------------| +| 视频分镜 | `Segment` | Layer 3-6 | `shot`, `scene_desc` | +| 数字人形象 | `Human` / `Avatar` | Layer 3-6(DB 用 `avatar`,API 用 `human_id`) | `element`, `character` | +| 调度器工作单元 | `Job` | Layer 4 | `task` | +| 供应商侧任务 | `ProviderJob` | Layer 2 | `kling_task`, `volc_task` | +| 供应商任务 ID | `provider_task_id` | Layer 2-4 | `kling_task_id`, `video_task_id`, `image_task_id` | +| 分镜状态 | `SegmentStatus` | Layer 3-4 | 裸字符串 | +| 调度器状态 | `JobStatus` | Layer 4 | 裸字符串 | +| 形象克隆状态 | `AvatarCloneStatus` | Layer 3 | 裸字符串 | +| Kling 原始状态 | `KlingTaskStatus` | **Layer 2 仅限** | 泄漏到 Layer 3+ | + +### 分层禁令 + +1. **API 层 (`app/api/v1/`)**:禁止出现 `element_id`, `kling_task_id`, `shot_type`, `omni` +2. **Scheduler 层 (`app/scheduler/`)**:禁止出现 `task_id`(应为 `job_id`),禁止构造供应商 prompt 语法 +3. **Service 层 (`app/services/`)**:禁止出现 `<<>>` 等供应商专用语法 +4. **Provider 层 (`app/ai/providers/`)**:允许使用 `element_id`, `kling_task_id`, `KlingTaskStatus` + +### 类型禁令 + +- 跨层传递的接口禁止裸用 `dict[str, Any]`。`params`、`result`、`changes` 等字段必须使用 Pydantic 模型或 TypedDict +- 状态字段禁止使用裸字符串,必须使用对应的 `StrEnum` +- CRUD 层 `obj_in` 禁止裸字典,必须使用 `CreateSchema` / `UpdateSchema` diff --git a/docs/.DS_Store b/docs/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 原文档:https://www.anytocopy.com/account/api/docs +> +> 功能:支持 50+ 平台视频文案提取、视频去水印 + +## 概述 + +AnyToCopy API 提供视频/图片文案提取功能,支持抖音、小红书、快手等 50+ 平台。 + +**核心功能**: +- 视频文案提取(语音转文字) +- 视频去水印下载 +- 图片去水印下载 +- 支持 50+ 内容平台 + +--- + +## 基础信息 + +| 项目 | 内容 | +|------|------| +| **Base URL** | `https://api.anytocopy.com/vip/open-api/v1` | +| **协议** | HTTPS | +| **数据格式** | JSON | + +### 鉴权方式 + +在请求头中携带 API Key 和 Secret: + +```http +X-API-Key: your_api_key +X-API-Secret: your_api_secret +``` + +--- + +## 接口列表 + +### 1. 提交视频文案提取任务 + +创建提取任务,返回 `taskId` 用于后续查询。 + +#### 请求 + +| 项目 | 内容 | +|------|------| +| **Method** | POST | +| **Endpoint** | `/video/extract` | + +#### 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| `workUrl` | String | 是 | 作品链接(支持抖音、小红书等) | +| `taskType` | String | 否 | 任务类型,默认 `TEXT`(文案提取) | + +#### curl 示例 + +```bash +curl -X POST 'https://api.anytocopy.com/vip/open-api/v1/video/extract?workUrl=https://v.douyin.com/xxx&taskType=TEXT' \ + -H 'X-API-Key: your_api_key' \ + -H 'X-API-Secret: your_api_secret' +``` + +#### 响应示例 + +**成功响应(HTTP 200)**: +```json +{ + "msg": "任务已提交", + "code": 200, + "data": "2008802706718072832" +} +``` + +**失败响应(并发限制)**: +```json +{ + "msg": "您的并发任务已达上限(5/5),请等待任务完成后再试", + "code": 500 +} +``` + +--- + +### 2. 查询任务状态和结果 + +根据 `taskId` 查询任务进度与提取结果。 + +#### 请求 + +| 项目 | 内容 | +|------|------| +| **Method** | GET | +| **Endpoint** | `/video/query` | + +#### 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| `taskId` | String | 是 | 任务 ID(提交任务时返回) | + +#### curl 示例 + +```bash +curl -X GET 'https://api.anytocopy.com/vip/open-api/v1/video/query?taskId=2008802706718072832' \ + -H 'X-API-Key: your_api_key' \ + -H 'X-API-Secret: your_api_secret' +``` + +#### 响应示例 + +**任务完成(SUCCESS)**: +```json +{ + "msg": "操作成功", + "code": 200, + "data": { + "taskId": "2008802706718072832", + "title": "小个子女生如何逆袭第一眼大美女", + "content": "#听劝改造[话题]# #如何找到自己的风格", + "videoUrl": "https://sns-video-bd.xhscdn.com/f0370019b934b9b6e_258.mp4", + "videoUrlList": ["https://sns-video-bd.xhscdn.com/stream/79258.mp4"], + "imageUrlList": ["https://ci.xiaohongshu.com/1040g2sg31r0hdqhjnge05q"], + "cover": "https://ci.xiaohongshu.com/1040g2sg31r0hdqhjnge05", + "textContent": "小个子女生真的不要再和别人卷身高上的天赋...", + "platform": "xhs", + "audioUrl": "https://pub-6026ae78487b47e5bd4a5b8a0d9ae5aa.r2.dev/audio.mp3", + "duration": 156.36, + "workType": "video", + "status": "SUCCESS", + "errorMessage": "视频处理成功!", + "createBy": "60227", + "createTime": "2026-01-07 15:27:42" + } +} +``` + +**任务处理中(WAITING)**: +```json +{ + "msg": "操作成功", + "code": 200, + "data": { + "taskId": "2008805155734429696", + "title": "今日摘抄,不知道原创是谁,太多了", + "content": "今日摘抄,不知道原创是谁,太多了,可以在", + "videoUrl": "https://sns-video-qc.xhscdn.com/stream/79/258.mp4", + "videoUrlList": ["https://sns-video-qc.xhscdn.com/stream/79/258.mp4"], + "imageUrlList": ["https://ci.xiaohongshu.com/spectrum/1040g0k031qo"], + "cover": "https://ci.xiaohongshu.com/spectrum/1040g0k031qoj7pr9gm905", + "textContent": "", + "platform": "xhs", + "audioUrl": null, + "duration": null, + "workType": "video", + "status": "WAITING", + "errorMessage": "作品内容提取中...", + "createBy": "60227", + "createTime": "2026-01-07 15:37:26" + } +} +``` + +**任务失败(FAILURE)**: +```json +{ + "msg": "操作成功", + "code": 200, + "data": { + "taskId": "2008805155734429696", + "status": "FAILURE", + "errorMessage": "任务执行失败" + } +} +``` + +--- + +## 响应状态码 + +| 状态码 | 说明 | 场景 | +|--------|------|------| +| 200 | 成功 | 任务创建成功或查询成功 | +| 500 | 失败 | 并发任务已达上限或其他错误 | + +--- + +## 任务状态说明 + +| 状态值 | 说明 | 处理建议 | +|--------|------|----------| +| `WAITING` | 任务等待中或处理中 | 继续轮询查询任务状态 | +| `PROCESSING` | 任务处理中 | 继续轮询查询任务状态 | +| `SUCCESS` | 任务执行成功 | 可获取完整的提取结果数据 | +| `FAILED` / `FAILURE` | 任务执行失败 | 检查 `errorMessage` 字段获取失败原因 | + +--- + +## 响应字段说明 + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| `taskId` | String | 任务唯一标识 | +| `title` | String | 作品标题 | +| `content` | String | 作品正文内容 | +| `textContent` | String | 视频语音转文字文案(任务完成后) | +| `videoUrl` | String | 视频下载链接(无水印) | +| `audioUrl` | String | 音频文件链接(任务完成后) | +| `imageUrlList` | Array | 图片链接列表 | +| `cover` | String | 封面图片链接 | +| `platform` | String | 平台标识(xhs、douyin 等) | +| `duration` | Number | 视频时长(秒) | +| `workType` | String | 作品类型(video、image) | +| `status` | String | 任务状态(WAITING、SUCCESS、FAILURE) | +| `errorMessage` | String | 状态描述或错误信息 | +| `createBy` | String | 创建者 ID | +| `createTime` | String | 创建时间 | + +--- + +## 接口使用流程 + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ 1. 提交任务 │ --> │ 2. 轮询查询 │ --> │ 3. 处理结果 │ +└─────────────┘ └─────────────┘ └─────────────┘ + │ │ │ + ▼ ▼ ▼ +POST /video/extract GET /video/query status = SUCCESS +获取 taskId 每 3-5 秒查询一次 获取完整结果 +``` + +### 推荐调用流程 + +1. **提交任务** + - 调用 `POST /video/extract` 接口,传入作品链接 + - 成功后返回 `taskId`,用于后续查询 + +2. **轮询查询** + - 使用返回的 `taskId` 调用 `GET /video/query` 接口 + - 建议每隔 **3-5 秒** 查询一次任务状态 + +3. **处理结果** + - 当 `status` 为 `SUCCESS` 时,获取完整的提取结果(标题、正文、视频、音频等) + - 若为 `FAILURE`,检查 `errorMessage` 了解失败原因 + +--- + +## 最佳实践 + +- **轮询间隔建议**:3-5 秒,避免过于频繁请求 +- **最大轮询次数**:建议设置 60 次上限,避免无限轮询 +- **安全保管**:妥善保管 API Key 和 Secret,不要泄露到客户端 +- **并发限制**:并发任务上限为 5 个,合理安排任务提交 + +--- + +## 支持平台 + +支持 50+ 平台,主要包括: + +| 平台 | 标识 | 说明 | +|------|------|------| +| 小红书 | xhs | 视频、图文 | +| 抖音 | douyin | 视频 | +| 快手 | kuaishou | 视频 | +| ... | ... | 更多平台 | + +--- + +## Python 集成示例 + +```python +import asyncio +import aiohttp + +class AnyToCopyClient: + BASE_URL = "https://api.anytocopy.com/vip/open-api/v1" + + def __init__(self, api_key: str, api_secret: str): + self.api_key = api_key + self.api_secret = api_secret + self.headers = { + "X-API-Key": api_key, + "X-API-Secret": api_secret, + } + + async def submit_task(self, work_url: str, task_type: str = "TEXT") -> dict: + """提交视频文案提取任务""" + url = f"{self.BASE_URL}/video/extract" + params = {"workUrl": work_url, "taskType": task_type} + + async with aiohttp.ClientSession() as session: + async with session.post(url, headers=self.headers, params=params) as resp: + return await resp.json() + + async def query_task(self, task_id: str) -> dict: + """查询任务状态和结果""" + url = f"{self.BASE_URL}/video/query" + params = {"taskId": task_id} + + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=self.headers, params=params) as resp: + return await resp.json() + + async def extract_video(self, work_url: str, max_retries: int = 60) -> dict: + """完整的视频提取流程(提交 + 轮询)""" + # 1. 提交任务 + submit_result = await self.submit_task(work_url) + if submit_result.get("code") != 200: + raise Exception(f"提交任务失败: {submit_result.get('msg')}") + + task_id = submit_result["data"] + print(f"任务已提交,taskId: {task_id}") + + # 2. 轮询查询 + for i in range(max_retries): + await asyncio.sleep(3) # 每 3 秒查询一次 + + query_result = await self.query_task(task_id) + if query_result.get("code") != 200: + continue + + data = query_result.get("data", {}) + status = data.get("status") + + if status == "SUCCESS": + print(f"任务完成!") + return data + elif status == "FAILURE": + raise Exception(f"任务失败: {data.get('errorMessage')}") + else: + print(f"[{i+1}/{max_retries}] 任务处理中...") + + raise Exception("轮询超时,任务未完成") + + +# 使用示例 +async def main(): + client = AnyToCopyClient( + api_key="your_api_key", + api_secret="your_api_secret" + ) + + try: + result = await client.extract_video("https://v.douyin.com/xxxxx") + print(f"标题: {result['title']}") + print(f"文案: {result['textContent']}") + print(f"视频: {result['videoUrl']}") + except Exception as e: + print(f"错误: {e}") + +if __name__ == "__main__": + asyncio.run(main()) +``` diff --git a/docs/anytocopy-integration.md b/docs/anytocopy-integration.md new file mode 100644 index 0000000..b34f5e3 --- /dev/null +++ b/docs/anytocopy-integration.md @@ -0,0 +1,128 @@ +# AnyToCopy 视频文案提取集成 + +## 功能概述 + +脚本生成 API 现已支持自动识别视频链接并提取文案。 + +- **自动检测**:输入创作主题时自动检测是否为视频链接 +- **智能提取**:支持小红书、抖音、快手等 50+ 平台 +- **无缝集成**:提取的文案自动用于脚本生成 + +## 支持平台 + +| 平台 | 示例链接 | +|------|----------| +| 小红书 | `https://xhslink.com/xxx` | +| 抖音 | `https://v.douyin.com/xxx` | +| 快手 | `https://v.kuaishou.com/xxx` | +| 哔哩哔哩 | `https://b23.tv/xxx` | +| 微博 | `https://weibo.com/xxx` | + +## 使用方式 + +### 1. 普通文案生成(原有功能) + +```json +POST /api/v1/ai/scripts/generate +{ + "topic": "家装验收的5个细节", + "duration": 60, + "script_type": "professional" +} +``` + +### 2. 视频链接提取文案后生成 + +```json +POST /api/v1/ai/scripts/generate +{ + "topic": "https://v.douyin.com/AbC123", + "duration": 60, + "script_type": "professional" +} +``` + +**流程**: +1. 检测输入为视频链接 +2. 调用 AnyToCopy API 提取视频文案 +3. 使用提取的文案作为创作主题生成脚本 + +### 3. 混合输入(链接 + 说明) + +```json +POST /api/v1/ai/scripts/generate +{ + "topic": "参考这个视频的风格 https://v.douyin.com/AbC123,写一个关于装修验收的脚本", + "duration": 60, + "script_type": "professional" +} +``` + +**流程**: +1. 从文本中提取视频链接 +2. 提取视频文案 +3. 将提取的文案与原始说明结合生成脚本 + +## 流式生成(SSE) + +视频提取过程会显示在进度中: + +``` +data: {"type": "analyzing", "progress": 5, "message": "检测到视频链接,正在提取文案..."} + +data: {"type": "analyzing", "progress": 10, "message": "视频文案提取成功,共 1200 字符"} + +data: {"type": "generating", "progress": 15, "message": "正在创作脚本..."} +... +``` + +## 配置 + +在 `.env` 文件中配置 AnyToCopy API: + +```bash +# AnyToCopy 视频文案提取服务 +ANYTOCOPY_API_KEY=your-api-key +ANYTOCOPY_API_SECRET=your-api-secret +ANYTOCOPY_BASE_URL=https://api.anytocopy.com/vip/open-api/v1 +``` + +## 注意事项 + +1. **API Key**:需要从 AnyToCopy 官网获取 API Key +2. **并发限制**:AnyToCopy 限制并发任务数为 5 +3. **提取时间**:视频文案提取通常需要 10-30 秒 +4. **失败处理**:如果提取失败,会自动使用原始输入继续生成脚本 + +## 代码集成 + +### 服务层 + +```python +from app.services.anytocopy_service import get_anytocopy_service + +anytocopy = get_anytocopy_service() +result = await anytocopy.extract_text_from_input("https://v.douyin.com/xxx") + +if result["is_video_url"]: + extracted_text = result["extracted_text"] + # 使用提取的文案 +``` + +### 独立使用 AnyToCopy 服务 + +```python +from app.services.anytocopy_service import AnyToCopyService + +service = AnyToCopyService({ + "api_key": "your-key", + "api_secret": "your-secret", +}) + +# 提交任务 +result = await service.submit_task("https://v.douyin.com/xxx") +task_id = result["data"] + +# 查询结果 +query_result = await service.query_task(task_id) +``` diff --git a/docs/app-update-system.md b/docs/app-update-system.md new file mode 100644 index 0000000..b99c7ab --- /dev/null +++ b/docs/app-update-system.md @@ -0,0 +1,2402 @@ +# 应用自动更新系统开发文档 + +## 概述 + +本文档详细说明美家卡智影应用自动更新系统的完整实现方案,采用自建更新服务器 + 七牛云存储的架构,解决国内访问 GitHub 不稳定的问题。 + +**架构特点**: +- 轻量云账号 + 全本地业务数据 +- 七牛云存储更新包,稳定访问 +- FastAPI 提供更新检查和下载接口 +- Tauri 应用通过 API 获取更新并安装 +- 支持强制更新、下载统计等功能 + +--- + +## 一、架构设计 + +### 1.1 整体架构 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 桌面应用 │ +│ ┌──────────────────────┐ ┌──────────────────────────┐ │ +│ │ React 前端 │────────▶│ Rust 后端 │ │ +│ │ - 更新对话框 │ IPC │ - 检查/下载/安装 │ │ +│ └──────────────────────┘ └──────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ │ + │ HTTP │ + ▼ │ +┌─────────────────────────────────────────────────────────────────┐ +│ 后端服务层 │ +│ ┌──────────────────────┐ ┌──────────────────────────┐ │ +│ │ FastAPI 后端 │────────▶│ PostgreSQL 数据库 │ │ +│ │ - 更新 API │ │ - 版本/包/下载日志 │ │ +│ └──────────────────────┘ └──────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────┐ │ +│ │ 七牛云存储 │ │ +│ └──────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 1.2 更新流程 + +应用启动 → 检查更新 → 显示对话框 → 下载安装包 → 安装 → 重启应用 + +--- + +## 二、数据库设计 + +### 2.1 数据表结构 + +#### 2.1.1 app_releases - 版本发布记录 + +| 字段 | 类型 | 说明 | 约束 | +|------|------|------|------| +| id | SERIAL | 主键 | PRIMARY KEY | +| version | VARCHAR(20) | 版本号 (语义化版本) | NOT NULL, UNIQUE | +| release_date | TIMESTAMP | 发布时间 | NOT NULL | +| notes | TEXT | 更新说明 | NOT NULL | +| mandatory | BOOLEAN | 是否强制更新 | DEFAULT FALSE | +| created_at | TIMESTAMP | 创建时间 | DEFAULT NOW() | + +**示例数据**: +```sql +INSERT INTO app_releases (version, release_date, notes, mandatory) VALUES +('0.1.0', '2026-04-01 10:00:00', '初始版本发布', FALSE), +('0.1.1', '2026-04-14 10:00:00', '新功能:视频字幕压制\n修复:导出问题', FALSE), +('0.2.0', '2026-04-20 10:00:00', '新增:批量导出功能\n优化:性能提升 30%', FALSE); +``` + +#### 2.1.2 release_packages - 平台包信息 + +| 字段 | 类型 | 说明 | 约束 | +|------|------|------|------| +| id | SERIAL | 主键 | PRIMARY KEY | +| release_id | INTEGER | 关联版本发布 | FOREIGN KEY → app_releases.id | +| platform | VARCHAR(20) | 操作系统平台 | NOT NULL | +| architecture | VARCHAR(20) | CPU 架构 | NOT NULL | +| filename | VARCHAR(255) | 文件名 | NOT NULL | +| file_url | VARCHAR(500) | 七牛云下载 URL | NOT NULL | +| file_size | BIGINT | 文件大小(字节) | NOT NULL | +| file_hash | VARCHAR(64) | SHA256 哈希 | NOT NULL | +| download_count | INTEGER | 下载次数 | DEFAULT 0 | +| created_at | TIMESTAMP | 创建时间 | DEFAULT NOW() | + +**平台枚举值**: +- `darwin`: macOS +- `windows`: Windows +- `linux`: Linux + +**架构枚举值**: +- `x86_64`: 64位 Intel/AMD +- `arm64`: ARM64 (Apple Silicon) + +**示例数据**: +```sql +INSERT INTO release_packages (release_id, platform, architecture, filename, file_url, file_size, file_hash) VALUES +(2, 'darwin', 'x86_64', 'meijiaka_0.1.1_darwin_x86_64.dmg', + 'https://cdn.meijiaka.com/releases/meijiaka_0.1.1_darwin_x86_64.dmg', + 102400000, 'sha256:abc123...'), + +(2, 'windows', 'x86_64', 'meijiaka_0.1.1_windows_x86_64-setup.exe', + 'https://cdn.meijiaka.com/releases/meijiaka_0.1.1_windows_x86_64-setup.exe', + 115343360, 'sha256:def456...'), + +(2, 'linux', 'amd64', 'meijiaka_0.1.1_linux_amd64.AppImage', + 'https://cdn.meijiaka.com/releases/meijiaka_0.1.1_linux_amd64.AppImage', + 104857600, 'sha256:ghi789...'); +``` + +#### 2.1.3 update_downloads - 更新下载日志 + +| 字段 | 类型 | 说明 | 约束 | +|------| | | | +| id | SERIAL | 主键 | PRIMARY KEY | +| release_id | INTEGER | 关联版本发布 | FOREIGN KEY → app_releases.id | +| platform | VARCHAR(20) | 下载平台 | NOT NULL | +| app_version | VARCHAR(20) | 应用当前版本 | NOT NULL | +| user_id | INTEGER | 用户 ID (可选) | FOREIGN KEY → users.id | +| download_at | TIMESTAMP | 下载时间 | DEFAULT NOW() | + +### 2.2 索引设计 + +```sql +-- 快速查询最新版本 +CREATE INDEX idx_releases_release_date ON app_releases(release_date DESC); + +-- 平台包复合索引 +CREATE INDEX idx_packages_platform_arch ON release_packages(platform, architecture); + +-- 下载统计 +CREATE INDEX idx_downloads_release_id ON update_downloads(release_id); +``` + +### 2.3 初始化脚本 + +文件位置:`python-api/scripts/init_update_tables.sql` + +```sql +-- 创建版本发布记录表 +CREATE TABLE IF NOT EXISTS app_releases ( + id SERIAL PRIMARY KEY, + version VARCHAR(20) NOT NULL UNIQUE, + release_date TIMESTAMP NOT NULL, + notes TEXT NOT NULL, + mandatory BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT NOW() +); + +-- 创建平台包信息表 +CREATE TABLE IF NOT EXISTS release_packages ( + id SERIAL PRIMARY KEY, + release_id INTEGER NOT NULL REFERENCES app_releases(id) ON DELETE CASCADE, + platform VARCHAR(20) NOT NULL, + architecture VARCHAR(20) NOT NULL, + filename VARCHAR(255) NOT NULL, + file_url VARCHAR(500) NOT NULL, + file_size BIGINT NOT NULL, + file_hash VARCHAR(64) NOT NULL, + download_count INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT NOW() +); + +-- 创建更新下载日志表 +CREATE TABLE IF NOT EXISTS update_downloads ( + id SERIAL PRIMARY KEY, + release_id INTEGER NOT NULL REFERENCES app_releases(id) ON DELETE CASCADE, + platform VARCHAR(20) NOT NULL, + app_version VARCHAR(20) NOT NULL, + user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, + download_at TIMESTAMP DEFAULT NOW() +); + +-- 创建索引 +CREATE INDEX IF NOT EXISTS idx_releases_version ON app_releases(version); +CREATE INDEX IF NOT EXISTS idx_releases_release_date ON app_releases(release_date DESC); +CREATE INDEX IF NOT EXISTS idx_packages_platform_arch ON release_packages(platform, architecture); +CREATE INDEX IF NOT EXISTS idx_packages_release_id ON release_packages(release_id); +CREATE INDEX IF NOT EXISTS idx_downloads_release_id ON update_downloads(release_id); +CREATE INDEX IF NOT EXISTS idx_downloads_download_at ON update_downloads(download_at); + +-- 插入初始版本(可选) +INSERT INTO app_releases (version, release_date, notes, mandatory) +VALUES ('0.1.0', '2026-04-01 10:00:00', '初始版本发布', FALSE) +ON CONFLICT (version) DO NOTHING; +``` + +--- + +## 三、后端 API 设计 + +### 3.1 API 端点列表 + +| 方法 | 路径 | 说明 | 认证 | +|------|------|------|------| +| POST | `/api/v1/update/check` | 检查应用更新 | 无需认证 | +| GET | `/api/v1/update/download/{version}/{platform}` | 获取下载 URL 并记录 | 无需认证 | +| POST | `/api/v1/update/releases` | 创建新版本发布 | 需要管理员认证 | +| GET | `/api/v1/update/releases` | 获取所有版本列表 | 需要管理员认证 | +| DELETE | `/api/v1/update/releases/{version}` | 删除版本发布 | 需要管理员认证 | + +### 3.2 数据模型 + +#### 3.2.1 SQLAlchemy 模型 + +文件位置:`python-api/app/models/update.py` + +```python +from datetime import datetime +from typing import Optional +from sqlalchemy import DateTime, Boolean, Integer, String, Text, BigInteger, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship +from .base import Base + +class AppRelease(Base): + """应用版本发布记录""" + __tablename__ = "app_releases" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + version: Mapped[str] = mapped_column(String(20), unique=True, index=True) + release_date: Mapped[datetime] = mapped_column(DateTime, nullable=False) + notes: Mapped[str] = mapped_column(Text, nullable=False) + mandatory: Mapped[bool] = mapped_column(Boolean, default=False) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + + # 关系 + packages: Mapped[list["ReleasePackage"]] = relationship( + "ReleasePackage", + back_populates="release", + cascade="all, delete-orphan" + ) + downloads: Mapped[list["UpdateDownload"]] = relationship( + "UpdateDownload", + back_populates="release", + cascade="all, delete-orphan" + ) + +class ReleasePackage(Base): + """平台包信息""" + __tablename__ = "release_packages" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + release_id: Mapped[int] = mapped_column( + Integer, + ForeignKey("app_releases.id", ondelete="CASCADE"), + nullable=False + ) + platform: Mapped[str] = mapped_column(String(20), nullable=False) + architecture: Mapped[str] = mapped_column(String(20), nullable=False) + filename: Mapped[str] = mapped_column(String(255), nullable=False) + file_url: Mapped[str] = mapped_column(String(500), nullable=False) + file_size: Mapped[int] = mapped_column(BigInteger, nullable=False) + file_hash: Mapped[str] = mapped_column(String(64), nullable=False) + download_count: Mapped[int] = mapped_column(Integer, default=0) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + + # 关系 + release: Mapped["AppRelease"] = relationship("AppRelease", back_populates="packages") + +class UpdateDownload(Base): + """更新下载日志""" + __tablename__ = "update_downloads" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + release_id: Mapped[int] = mapped_column( + Integer, + ForeignKey("app_releases.id", ondelete="CASCADE"), + nullable=False + ) + platform: Mapped[str] = mapped_column(String(20), nullable=False) + app_version: Mapped[str] = mapped_column(String(20), nullable=False) + user_id: Mapped[Optional[int]] = mapped_column( + Integer, + ForeignKey("users.id", ondelete="SET NULL") + ) + download_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + + # 关系 + release: Mapped["AppRelease"] = relationship("AppRelease", back_populates="downloads") +``` + +#### 3.2.2 Pydantic Schemas + +文件位置:`python-api/app/schemas/update.py` + +```python +from datetime import datetime +from typing import Optional +from pydantic import BaseModel, Field + +class UpdateCheckRequest(BaseModel): + """更新检查请求""" + current_version: str = Field(..., description="当前应用版本") + platform: str = Field(..., description="操作系统平台: darwin, windows, linux") + architecture: Optional[str] = Field(None, description="CPU架构: x86_64, arm64") + +class PackageInfo(BaseModel): + """包信息""" + platform: str + architecture: str + file_url: str + file_size: int + file_hash: str + +class UpdateInfoResponse(BaseModel): + """更新信息响应""" + version: str + release_date: datetime + notes: str + mandatory: bool + packages: list[PackageInfo] + +class PackageCreate(BaseModel): + """包创建信息""" + platform: str + architecture: str + file_url: str + file_size: int + file_hash: str + +class ReleaseCreate(BaseModel): + """版本发布创建请求""" + version: str + notes: str + mandatory: bool = False + packages: list[PackageCreate] + +class ReleaseResponse(BaseModel): + """版本发布响应""" + id: int + version: str + release_date: datetime + notes: str + mandatory: bool + created_at: datetime + packages: list[PackageInfo] + +class DownloadResponse(BaseModel): + """下载信息响应""" + download_url: str + file_size: int + file_hash: str +``` + +### 3.3 API 路由实现 + +文件位置:`python-api/app/api/v1/update.py` + +```python +from fastapi import APIRouter, HTTPException, Depends, status +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select, and_ +from typing import Optional + +from ...deps import get_db +from ...models.update import AppRelease, ReleasePackage, UpdateDownload +from ...schemas.update import ( + UpdateCheckRequest, + UpdateInfoResponse, + ReleaseCreate, + ReleaseResponse, + DownloadResponse, + PackageInfo, + PackageCreate +) + +router = APIRouter() + +@router.post("/check", response_model=UpdateInfoResponse, status_code=status.HTTP_200_OK) +async def check_update( + request: UpdateCheckRequest, + db: AsyncSession = Depends(get_db) +): + """ + 检查应用更新 + + Args: + request: 包含当前版本、平台信息的请求 + + Returns: + 最新的版本信息,如果已是最新版本则返回 204 No Content + """ + # 查询最新版本 + result = await db.execute( + select(AppRelease) + .order_by(AppRelease.release_date.desc()) + .limit(1) + ) + latest_release = result.scalar_one_or_none() + + if not latest_release: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="No releases found" + ) + + # 如果已是最新版本 + if latest_release.version == request.current_version: + raise HTTPException( + status_code=status.HTTP_204_NO_CONTENT, + detail="Already up to date" + ) + + # 确定架构(如果未提供) + arch = request.architecture or _get_default_architecture(request.platform) + + # 查询对应平台的包 + result = await db.execute( + select(ReleasePackage).where( + and_( + ReleasePackage.release_id == latest_release.id, + ReleasePackage.platform == request.platform, + ReleasePackage.architecture == arch + ) + ) + ) + packages = result.scalars().all() + + if not packages: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"No package found for platform {request.platform} {arch}" + ) + + # 构建响应 + return UpdateInfoResponse( + version=latest_release.version, + release_date=latest_release.release_date, + notes=latest_release.notes, + mandatory=latest_release.mandatory, + packages=[ + PackageInfo( + platform=p.platform, + architecture=p.architecture, + file_url=p.file_url, + file_size=p.file_size, + file_hash=p.file_hash + ) + for p in packages + ] + ) + +@router.get("/download/{version}/{platform}", response_model=DownloadResponse) +async def get_download_url( + version: str, + platform: str, + db: AsyncSession = Depends(get_db) +): + """ + 获取下载 URL 并记录下载日志 + + Args: + version: 目标版本 + platform: 平台类型 + + Returns: + 包含下载 URL、文件大小和哈希的响应 + """ + # 查询版本信息 + result = await db.execute( + select(AppRelease).where(AppRelease.version == version) + ) + release = result.scalar_one_or_none() + + if not release: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Release not found" + ) + + # 查询对应平台的包 + result = await db.execute( + select(ReleasePackage).where( + and_( + ReleasePackage.release_id == release.id, + ReleasePackage.platform == platform + ) + ) + ) + package = result.scalar_one_or_none() + + if not package: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Package not found for platform {platform}" + ) + + # 增加下载计数 + package.download_count += 1 + + # 记录下载日志(可选,不阻塞主流程) + download_log = UpdateDownload( + release_id=release.id, + platform=platform, + app_version=version + ) + db.add(download_log) + + await db.commit() + + return DownloadResponse( + download_url=package.file_url, + file_size=package.file_size, + file_hash=package.file_hash + ) + +@router.post("/releases", response_model=ReleaseResponse) +async def create_release( + release: ReleaseCreate, + db: AsyncSession = Depends(get_db) +): + """ + 创建新版本发布 + + Args: + release: 版本发布信息 + + Returns: + 创建的版本发布信息 + """ + # 检查版本是否已存在 + result = await db.execute( + select(AppRelease).where(AppRelease.version == release.version) + ) + if result.scalar_one_or_none(): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Version already exists" + ) + + # 创建发布记录 + new_release = AppRelease( + version=release.version, + release_date=datetime.utcnow(), + notes=release.notes, + mandatory=release.mandatory + ) + db.add(new_release) + await db.flush() # 获取 ID + + # 创建包记录 + for pkg in release.packages: + package = ReleasePackage( + release_id=new_release.id, + platform=pkg.platform, + architecture=pkg.architecture, + filename=pkg.file_url.split("/")[-1], + file_url=pkg.file_url, + file_size=pkg.file_size, + file_hash=pkg.file_hash + ) + db.add(package) + + await db.commit() + + # 构建响应 + return ReleaseResponse( + id=new_release.id, + version=new_release.version, + release_date=new_release.release_date, + notes=new_release.notes, + mandatory=new_release.mandatory, + created_at=new_release.created_at, + packages=[ + PackageInfo( + platform=pkg.platform, + architecture=pkg.architecture, + file_url=pkg.file_url, + file_size=pkg.file_size, + file_hash=pkg.file_hash + ) + for pkg in release.packages + ] + ) + +@router.get("/releases", response_model=list[ReleaseResponse]) +async def list_releases( + db: AsyncSession = Depends(get_db) +): + """ + 获取所有版本发布列表 + + Returns: + 所有版本发布信息列表 + """ + result = await db.execute( + select(AppRelease).order_by(AppRelease.release_date.desc()) + ) + releases = result.scalars().all() + + responses = [] + for release in releases: + # 获取包信息 + result = await db.execute( + select(ReleasePackage).where( + ReleasePackage.release_id == release.id + ) + ) + packages = result.scalars().all() + + responses.append(ReleaseResponse( + id=release.id, + version=release.version, + release_date=release.release_date, + notes=release.notes, + mandatory=release.mandatory, + created_at=release.created_at, + packages=[ + PackageInfo( + platform=p.platform, + architecture=p.architecture, + file_url=p.file_url, + file_size=p.file_size, + file_hash=p.file_hash + ) + for p in packages + ] + )) + + return responses + +@router.delete("/releases/{version}") +async def delete_release( + version: str, + db: AsyncSession = Depends(get_db) +): + """ + 删除版本发布 + + Args: + version: 要删除的版本号 + + Returns: + 操作结果 + """ + result = await db.execute( + select(AppRelease).where(AppRelease.version == version) + ) + release = result.scalar_one_or_none() + + if not release: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Release not found" + ) + + await db.delete(release) + await db.commit() + + return {"status": "success", "message": f"Release {version} deleted"} + +# 辅助函数 +def _get_default_architecture(platform: str) -> str: + """获取系统架构默认值""" + if platform == "darwin": + return "universal" # macOS 默认 universal + elif platform == "linux": + return "amd64" # Linux 默认 amd64 + else: + return "x86_64" # Windows 默认 x86_64 +``` + +### 3.4 注册路由 + +在 `python-api/app/main.py` 中注册: + +```python +from app.api.v1.update import router as update_router + +app.include_router(update_router, prefix="/api/v1/update", tags=["更新"]) +``` + +--- + +## 四、Rust 后端更新逻辑 + +### 4.1 模块结构 + +``` +tauri-app/src-tauri/src/ +├── updater.rs # 更新模块主文件 +├── lib.rs # 命令注册 +└── Cargo.toml # 依赖配置 +``` + +### 4.2 依赖配置 + +在 `tauri-app/src-tauri/Cargo.toml` 中添加: + +```toml +[dependencies] +# 现有依赖... +sha2 = "0.10" # SHA256 哈希计算 +tokio = { version = "1", features = ["fs", "io-util"] } # 异步 I/O +dirs = "5" # 获取系统目录路径 +``` + +### 4.3 更新模块实现 + +文件位置:`tauri-app/src-tauri/src/updater.rs` + +```rust +use tauri::{AppHandle, Emitter}; +use reqwest::{self, Client}; +use std::fs::{self, File}; +use std::io::Write; +use std::path::PathBuf; +use serde::{Deserialize, Serialize}; +use tokio::io::AsyncReadExt; +use sha2::{Sha256, Digest}; + +// ============ 数据结构 ============ + +#[derive(Debug, Serialize, Deserialize)] +pub struct PackageInfo { + pub platform: String, + pub architecture: String, + pub file_url: String, + pub file_size: u64, + pub file_hash: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct UpdateInfo { + pub version: String, + pub release_date: String, + pub notes: String, + pub mandatory: bool, + pub packages: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct DownloadProgress { + pub downloaded: u64, + pub total: u64, + pub percentage: f32, +} + +// ============ Tauri 命令 ============ + +/// 检查应用更新 +#[tauri::command] +pub async fn check_update( + app: AppHandle, + current_version: String, +) -> Result, String> { + // 获取平台信息 + let platform = std::env::consts::OS; + let platform_name = match platform { + "macos" => "darwin", + "windows" => "windows", + "linux" => "linux", + _ => return Err(format!("Unsupported platform: {}", platform)), + }; + + // 获取架构信息 + let arch = if cfg!(target_arch = "x86_64") { + "x86_64" + } else if cfg!(target_arch = "aarch64") { + "arm64" + } else { + return Err("Unsupported architecture".to_string()); + }; + + // 调用 FastAPI 检查更新 + let client = Client::new(); + let api_url = "http://localhost:8080"; // 从配置读取 + + let url = format!( + "{}/api/v1/update/check", + api_url + ); + + let response = client + .post(&url) + .json(&serde_json::json!({ + "current_version": current_version, + "platform": platform_name, + "architecture": arch + })) + .send() + .await + .map_err(|e| format!("Failed to check update: {}", e))?; + + if response.status() == 204 { + return Ok(None); // 已是最新版本 + } + + let update_info: UpdateInfo = response + .json() + .await + .map_err(|e| format!("Failed to parse update info: {}", e))?; + + Ok(Some(update_info)) +} + +/// 下载更新包 +#[tauri::command] +pub async fn download_update( + app: AppHandle, + url: String, + file_hash: String, + expected_size: u64, +) -> Result { + // 创建更新目录 + let cache_dir = app.path().app_cache_dir() + .map_err(|e| format!("Failed to get cache dir: {}", e))?; + + let updates_dir = cache_dir.join("updates"); + fs::create_dir_all(&updates_dir) + .map_err(|e| format!("Failed to create updates dir: {}", e))?; + + // 从 URL 提取文件名 + let filename = url.split('/').last() + .unwrap_or("update.pkg") + .to_string(); + + let save_path = updates_dir.join(&filename); + + // 检查是否已下载并验证 + if save_path.exists() { + if verify_file_hash(&save_path, &file_hash).await { + return Ok(save_path.to_string_lossy().to_string()); + } + // 哈希不匹配,删除重新下载 + fs::remove_file(&save_path)?; + } + + // 发起下载请求 + let client = Client::new(); + let mut response = client + .get(&url) + .send() + .await + .map_err(|e| format!("Failed to download: {}", e))?; + + let total_size = expected_size; + let mut downloaded = 0u64; + let mut file = File::create(&save_path) + .map_err(|e| format!("Failed to create file: {}", e))?; + + // 流式下载并报告进度 + while let Some(chunk_result) = response.chunk().await { + let chunk = chunk_result + .map_err(|e| format!("Download error: {}", e))?; + + file.write_all(&chunk) + .map_err(|e| format!("Write error: {}", e))?; + + downloaded += chunk.len() as u64; + + // 发送进度事件 + let percentage = if total_size > 0 { + (downloaded as f32 / total_size as f32) * 100.0 + } else { + 0.0 + }; + + let _ = app.emit("download-progress", DownloadProgress { + downloaded, + total: total_size, + percentage, + }); + } + + // 验证文件哈希 + if !verify_file_hash(&save_path, &file_hash).await { + fs::remove_file(&save_path)?; + return Err("File hash verification failed".to_string()); + } + + Ok(save_path.to_string_lossy().to_string()) +} + +/// 安装更新包 +#[tauri::command] +pub async fn install_update( + app: AppHandle, + package_path: String, +) -> Result<(), String> { + let package_path = PathBuf::from(package_path); + + #[cfg(target_os = "macos")] + { + install_macos(app, package_path).await?; + } + + #[cfg(target_os = "windows")] + { + install_windows(app, package_path).await?; + } + + #[cfg(target_os = "linux")] + { + install_linux(app, package_path).await?; + } + + Ok(()) +} + +// ============ 平台特定实现 ============ + +#[cfg(target_os = "macos")] +async fn install_macos(app: AppHandle, package_path: PathBuf) -> Result<(), String> { + use std::process::Command; + use std::thread; + use std::time::Duration; + + // macOS: 使用 hdiutil 挂载 dmg 并复制应用到 ~/Applications + let mount_dir = PathBuf::from("/tmp/meijiaka_mount"); + + // 确保挂载目录不存在 + if mount_dir.exists() { + let _ = Command::new("hdiutil") + .args(["detach", mount_dir.to_str().unwrap(), "-force"]) + .status(); + } + + // 挂载 DMG + let output = Command::new("hdiutil") + .args([ + "attach", + "-readonly", + "-nobrowse", + "-mountpoint", + mount_dir.to_str().unwrap(), + package_path.to_str().unwrap(), + ]) + .output() + .map_err(|e| format!("Failed to mount DMG: {}", e))?; + + if !output.status.success() { + return Err(format!("Failed to mount DMG: {}", + String::from_utf8_loss(output.stderr.as_slice()))); + } + + // 查找应用文件 + let app_path = find_app_in_dmg(&mount_dir)?; + let app_name = app_path.file_name() + .ok_or("Invalid app path")? + .to_string_lossy() + .to_string(); + + // 用户目录 Applications + let dest_dir = dirs::home_dir() + .ok_or("Failed to get home directory")? + .join("Applications"); + + fs::create_dir_all(&dest_dir) + .map_err(|e| format!("Failed to create Applications dir: {}", e))?; + + let dest_path = dest_dir.join(&app_name); + + // 删除旧版本 + if dest_path.exists() { + let _ = Command::new("rm") + .args(["-rf", dest_path.to_str().unwrap()]) + .status(); + } + + // 复制应用 + let output = Command::new("cp") + .args(["-R", app_path.to_str().unwrap(), dest_dir.to_str().unwrap()]) + .output() + .map_err(|e| format!("Failed to copy app: {}", e))?; + + if !output.status.success() { + return Err("Failed to copy app".to_string()); + } + + // 卸载 DMG + let _ = Command::new("hdiutil") + .args(["detach", mount_dir.to_str().unwrap(), "-force"]) + .status(); + + // 延迟启动新版本 + thread::spawn(move || { + thread::sleep(Duration::from_secs(2)); + let _ = Command::new("open") + .args(["-a", &app_name]) + .status(); + }); + + // 退出当前应用 + app.exit(0); + + Ok(()) +} + +#[cfg(target_os = "macos")] +fn find_app_in_dmg(mount_dir: &PathBuf) -> Result { + use std::fs; + + let entries = fs::read_dir(mount_dir) + .map_err(|e| format!("Failed to read mount dir: {}", e))?; + + for entry in entries { + let entry = entry.map_err(|e| format!("Failed to read entry: {}", e))?; + let path = entry.path(); + + // 查找 .app 目录 + if path.extension().and_then(|s| s.to_str()) == Some("app") { + return Ok(path); + } + + // 递归查找 + if path.is_dir() { + if let Ok(app_path) = find_app_in_dmg(&path) { + return Ok(app_path); + } + } + } + + Err("App not found in DMG".to_string()) +} + +#[cfg(target_os = "windows")] +async fn install_windows(app: AppHandle, package_path: PathBuf) -> Result<(), String> { + use std::process::Command; + use std::thread; + use std::time::Duration; + + // Windows: 使用 NSIS 安装包 + let _ = Command::new(package_path.to_str().unwrap()) + .args(["/S"]) // 静默安装 + .spawn() + .map_err(|e| format!("Failed to start installer: {}", e))?; + + // 延迟退出,让安装程序完成 + thread::sleep(Duration::from_secs(2)); + + app.exit(0); + + Ok(()) +} + +#[cfg(target_os = "linux")] +async fn install_linux(app: AppHandle, package_path: PathBuf) -> Result<(), String> { + use std::process::Command; + use std::thread; + use std::time::Duration; + + // Linux: 提取 AppImage 并设置执行权限 + let home_dir = dirs::home_dir() + .ok_or("Failed to get home directory")?; + + let app_dir = home_dir.join("Applications"); + fs::create_dir_all(&app_dir)?; + + let filename = package_path.file_name() + .ok_or("Invalid package path")?; + + let dest_path = app_dir.join(filename); + + // 复制文件 + fs::copy(&package_path, &dest_path)?; + + // 设置执行权限 + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = fs::metadata(&dest_path)?.permissions(); + perms.set_mode(perms.mode() | 0o111); + fs::set_permissions(&dest_path, perms)?; + } + + // 启动新版本 + thread::spawn(move || { + thread::sleep(Duration::from_secs(2)); + let _ = Command::new(dest_path.to_str().unwrap()) + .status(); + }); + + app.exit(0); + + Ok(()) +} + +// ============ 工具函数 ============ + +/// 验证文件 SHA256 哈希 +async fn verify_file_hash(file_path: &PathBuf, expected_hash: &str) -> bool { + use tokio::fs; + + let file = fs::File::open(file_path).await; + if file.is_err() { + return false; + } + + let mut file = file.unwrap(); + let mut hasher = Sha256::new(); + let mut buffer = [0u8; 8192]; + + loop { + let n = file.read(&mut buffer).await.unwrap_or(0); + if n == 0 { + break; + } + hasher.update(&buffer[..n]); + } + + let actual_hash = format!("sha256:{:x}", hasher.finalize()); + actual_hash == expected_hash +} +``` + +**注意**:上述示例代码混合使用了同步和异步 I/O。在生产环境中,建议统一使用异步 I/O 以获得更好的性能。统一后的代码需要使用 `tokio::fs` 替代 `std::fs`,并使用 `tokio::process::Command` 替代 `std::process::Command`。 + +### 4.4 注册命令 + +在 `tauri-app/src-tauri/src/lib.rs` 中注册命令: + +```rust +mod updater; + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_opener::init()) + .invoke_handler(tauri::generate_handler![ + // ... 其他命令 + updater::check_update, + updater::download_update, + updater::install_update + ]) + .setup(|app| { + #[cfg(debug_assertions)] + { + let window = app.get_webview_window("main").unwrap(); + window.open_devtools(); + } + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +--- + +## 五、前端实现 + +### 5.1 更新状态管理 + +文件位置:`tauri-app/src/store/updateStore.ts` + +```typescript +import { create } from 'zustand'; +import { invoke } from '@tauri-apps/api/core'; +import { listen } from '@tauri-apps/api/event'; + +interface UpdatePackage { + platform: string; + architecture: string; + file_url: string; + file_size: number; + file_hash: string; +} + +interface UpdateInfo { + version: string; + release_date: string; + notes: string; + mandatory: boolean; + packages: UpdatePackage[]; +} + +interface DownloadProgress { + downloaded: number; + total: number; + percentage: number; +} + +interface UpdateState { + updateAvailable: boolean; + updateInfo: UpdateInfo | null; + downloadProgress: DownloadProgress; + downloading: boolean; + checking: boolean; + installing: boolean; + downloadedPath: string | null; + + checkUpdate: (currentVersion: string) => Promise; + downloadUpdate: () => Promise; + installUpdate: () => Promise; + dismissUpdate: () => void; +} + +export const useUpdateStore = create((set, get) => ({ + updateAvailable: false, + updateInfo: null, + downloadProgress: { downloaded: 0, total: 0, percentage: 0 }, + downloading: false, + checking: false, + installing: false, + downloadedPath: null, + + checkUpdate: async (currentVersion: string) => { + set({ checking: true }); + try { + const updateInfo = await invoke('check_update', { + currentVersion, + }); + + if (updateInfo) { + set({ updateAvailable: true, updateInfo }); + } else { + set({ updateAvailable: false, updateInfo: null }); + } + } catch (error) { + console.error('Check update failed:', error); + set({ updateAvailable: false, updateInfo: null }); + } finally { + set({ checking: false }); + } + }, + + downloadUpdate: async () => { + const { updateInfo } = get(); + if (!updateInfo) { + throw new Error('No update available'); + } + + // 获取当前平台的包 + const platform = process.platform === 'darwin' ? 'darwin' : + process.platform === 'win32' ? 'windows' : 'linux'; + + const arch = process.arch === 'arm64' ? 'arm64' : 'x86_64'; + + const pkg = updateInfo.packages.find( + p => p.platform === platform && p.architecture === arch + ); + + if (!pkg) { + throw new Error(`No package found for ${platform} ${arch}`); + } + + set({ downloading: true, downloadProgress: { downloaded: 0, total: pkg.file_size, percentage: 0 } }); + + try { + // 监听下载进度 + const unlisten = await listen('download-progress', (event) => { + set({ downloadProgress: event.payload }); + }); + + const downloadedPath = await invoke('download_update', { + url: pkg.file_url, + fileHash: pkg.file_hash, + expectedSize: pkg.file_size, + }); + + await unlisten(); + set({ downloadedPath }); + } catch (error) { + console.error('Download failed:', error); + throw error; + } finally { + set({ downloading: false }); + } + }, + + installUpdate: async () => { + const { downloadedPath } = get(); + if (!downloadedPath) { + throw new Error('No downloaded package'); + } + + set({ installing: true }); + try { + await invoke('install_update', { + packagePath: downloadedPath, + }); + // 安装后应用会重启,这里不会执行 + } catch (error) { + console.error('Install failed:', error); + throw error; + } finally { + set({ installing: false }); + } + }, + + dismissUpdate: () => { + set({ updateAvailable: false, updateInfo: null }); + }, +})); +``` + +### 5.2 更新对话框组件 + +文件位置:`tauri-app/src/components/UpdateDialog.tsx` + +```tsx +import { useEffect } from 'react'; +import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Progress } from '@/components/ui/progress'; +import { useUpdateStore } from '@/store/updateStore'; + +export function UpdateDialog() { + const { + updateAvailable, + updateInfo, + downloadProgress, + downloading, + checking, + installing, + checkUpdate, + downloadUpdate, + installUpdate, + dismissUpdate, + } = useUpdateStore(); + + // 当前应用版本(从 package.json 或构建时注入) + const CURRENT_VERSION = import.meta.env.VITE_APP_VERSION || '0.1.0'; + + useEffect(() => { + // 启动时检查更新 + checkUpdate(CURRENT_VERSION); + }, []); + + if (!updateAvailable) return null; + + const isMandatory = updateInfo?.mandatory; + + return ( + !open && !isMandatory && dismissUpdate()}> + + + {checking ? '检查更新...' : + downloading ? '下载更新中...' : + installing ? '安装更新...' : + `发现新版本 ${updateInfo?.version}`} + + +
+ {!checking && updateInfo && ( + <> +
+ {updateInfo.notes} +
+ + {downloading && ( +
+ +
+ {Math.round(downloadProgress.percentage)}% + ({(downloadProgress.downloaded / 1024 / 1024).toFixed(1)} MB / + {(downloadProgress.total / 1024 / 1024).toFixed(1)} MB) +
+
+ )} + +
+ {!downloading && !installing && ( + <> + {!isMandatory && ( + + )} + + + )} + + {!checking && !downloading && installing && ( + + )} + + {downloading && ( + + )} +
+ + {isMandatory && !installing && !downloading && ( +
+ 此版本为强制更新,必须安装后才能继续使用 +
+ )} + + )} +
+
+
+ ); +} +``` + +### 5.3 设置页面集成 + +在设置页面添加手动检查更新按钮: + +```tsx +import { useUpdateStore } from '@/store/updateStore'; +import { Button } from '@/components/ui/button'; + +function SettingsPage() { + const { checkUpdate, updateAvailable, updateInfo } = useUpdateStore(); + + const CURRENT_VERSION = import.meta.env.VITE_APP_VERSION || '0.1.0'; + + return ( +
+
+
+

应用更新

+

+ 当前版本:{CURRENT_VERSION} +

+
+ +
+ + {updateAvailable && updateInfo && ( +
+

发现新版本 {updateInfo.version}

+

+ {updateInfo.notes} +

+
+ )} +
+ ); +} +``` + +--- + +## 六、七牛云集成 + +### 6.1 七牛云配置 + +在 `python-api/.env` 中添加: + +```bash +# 七牛云配置 +QINIU_ACCESS_KEY=your-access-key +QINIU_SECRET_KEY=your-secret-key +QINIU_BUCKET_NAME=meijiaka-releases +QINIU_BUCKET_DOMAIN=cdn.meijiaka.com +``` + +### 6.2 七牛云服务封装 + +文件位置:`python-api/app/services/qiniu_service.py` + +```python +import os +import hashlib +from typing import Optional +from qiniu import Auth, BucketManager + +class QiniuService: + """七牛云服务封装""" + + def __init__(self): + access_key = os.getenv('QINIU_ACCESS_KEY') + secret_key = os.getenv('QINIU_SECRET_KEY') + self.bucket_name = os.getenv('QINIU_BUCKET_NAME') + self.domain = os.getenv('QINIU_BUCKET_DOMAIN') + + self.auth = Auth(access_key, secret_key) + self.bucket = BucketManager(self.auth) + + def get_file_url(self, key: str) -> str: + """获取文件访问 URL""" + return f"https://{self.domain}/{key}" + + def upload_file(self, local_path: str, key: str) -> dict: + """上传文件到七牛云""" + from qiniu import put_file_v2, etag + + token = self.auth.upload_token(self.bucket_name, key, 3600) + ret, info = put_file_v2(token, key, local_path, version='v2') + + if ret is None: + raise Exception(f"上传失败: {info}") + + return { + "key": ret['key'], + "hash": ret['hash'], + "url": self.get_file_url(key) + } + + def get_file_info(self, key: str) -> dict: + """获取文件信息""" + ret, info = self.bucket.stat(self.bucket_name, key) + if ret is None: + raise Exception(f"获取文件信息失败: {info}") + return ret + + def get_file_hash(self, local_path: str) -> str: + """计算本地文件 SHA256 哈希""" + sha256_hash = hashlib.sha256() + with open(local_path, "rb") as f: + for byte_block in iter(lambda: f.read(4096), b""): + sha256_hash.update(byte_block) + return f"sha256:{sha256_hash.hexdigest()}" + +# 全局单例 +_qiniu_service: Optional[QiniuService] = None + +def get_qiniu_service() -> QiniuService: + global _qiniu_service + if _qiniu_service is None: + _qiniu_service = QiniuService() + return _qiniu_service +``` + +--- + +## 七、部署流程 + +### 7.1 构建流程 + +```bash +# 1. 构建 Tauri 应用 +cd tauri-app +npm run tauri build + +# 2. 构建产物位置 +# macOS: src-tauri/target/release/bundle/dmg/ +# Windows: src-tauri/target/release/bundle/nsis/ +# Linux: src-tauri/target/release/bundle/appimage/ +``` + +### 7.2 上传到七牛云 + +文件位置:`python-api/scripts/upload_release.py` + +```python +#!/usr/bin/env python3 +""" +上传版本发布包到七牛云并创建版本发布记录 +""" + +import os +import sys +import argparse +import subprocess +from pathlib import Path + +# 添加项目路径 +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from app.services.qiniu_service import get_qiniu_service +import httpx + +def upload_package(local_path: str, version: str) -> dict: + """上传单个安装包""" + qiniu = get_qiniu_service() + + filename = Path(local_path).name + key = f"releases/{version}/{filename}" + + # 计算哈希 + file_hash = qiniu.get_file_hash(local_path) + + # 上传 + print(f"上传 {filename}...") + result = qiniu.upload_file(local_path, key) + + # 获取文件信息 + file_info = qiniu.get_file_info(key) + + return { + "platform": filename.split('_')[2], + "architecture": filename.split('_')[3].split('.')[0], + "file_url": result['url'], + "file_size": file_info['fsize'], + "file_hash": file_hash + } + +def create_release(version: str, notes: str, packages: list, mandatory: bool = False): + """创建版本发布""" + api_url = "http://localhost:8080/api/v1/update/releases" + + response = httpx.post(api_url, json={ + "version": version, + "notes": notes, + "mandatory": mandatory, + "packages": packages + }) + + if response.status_code == 200: + print(f"版本 {version} 发布成功!") + return response.json() + else: + print(f"创建版本发布失败: {response.text}") + sys.exit(1) + +def main(): + parser = argparse.ArgumentParser(description="上传版本发布包") + parser.add_argument("--version", required=True, help="版本号 (如: 0.1.1)") + parser.add_argument("--notes", required=True, help="更新说明") + parser.add_argument("--mandatory", action="store_true", help="强制更新") + parser.add_argument("--path", required=True, help="构建产物目录") + + args = parser.parse_args() + + build_dir = Path(args.path) + + # 查找所有安装包 + packages = [] + for file in build_dir.rglob("*"): + if file.suffix in ['.dmg', '.exe', '.AppImage']: + pkg_info = upload_package(str(file), args.version) + packages.append(pkg_info) + + # 创建版本发布 + create_release(args.version, args.notes, packages, args.mandatory) + +if __name__ == "__main__": + main() +``` + +使用方式: +```bash +cd python-api +python scripts/upload_release.py \ + --version 0.1.1 \ + --notes "新功能:视频字幕压制\n修复:导出问题" \ + --path ../tauri-app/src-tauri/target/release/bundle +``` + +### 7.3 GitHub Actions 自动化(多平台) + +文件位置:`.github/workflows/release.yml` + +```yaml +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + # macOS 构建 + build-macos: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + target: x86_64-apple-darwin + + - name: Install Tauri CLI + run: cargo install tauri-cli --version "^2.0" + + - name: Install Python dependencies + run: | + cd python-api + python -m venv venv + source venv/bin/activate + pip install -e ".[dev]" + pip install qiniu httpx + + - name: Install Node dependencies + run: | + cd tauri-app + npm install + + - name: Build Tauri app (macOS) + run: | + cd tauri-app + npm run tauri build --target x86_64-apple-darwin + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: macos-bundle + path: tauri-app/src-tauri/target/x86_64-apple-darwin/release/bundle/ + retention-days: 1 + + # Windows 构建 + build-windows: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + target: x86_64-pc-windows-msvc + + - name: Install Tauri CLI + run: cargo install tauri-cli --version "^2.0" + + - name: Install Python dependencies + run: | + cd python-api + python -m venv venv + venv\Scripts\pip install -e ".[dev]" + venv\Scripts\pip install qiniu httpx + + - name: Install Node dependencies + run: | + cd tauri-app + npm install + + - name: Build Tauri app (Windows) +) + run: | + cd tauri-app + npm run tauri build --target x86_64-pc-windows-msvc + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: windows-bundle + path: tauri-app/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/ + retention-days: 1 + + # Linux 构建 + build-linux: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + target: x86_64-unknown-linux-gnu + + - name: Install Tauri CLI + run: cargo install tauri-cli --version "^2.0" + + - name: Install Python dependencies + run: | + cd python-api + python -m venv venv + source venv/bin/activate + pip install -e ".[dev]" + pip install qiniu httpx + + - name: Install Node dependencies + run: | + cd tauri-app + npm install + + - name: Install Linux dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev + + - name: Build Tauri app (Linux) + run: | + cd tauri-app + npm run tauri build --target x86_64-unknown-linux-gnu + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: linux-bundle + path: tauri-app/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/ + retention-days: 1 + + # 统一发布 + release: + needs: [build-macos, build-windows, build-linux] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Download macOS artifacts + uses: actions/download-artifact@v4 + with: + name: macos-bundle + path: bundle/macos + + - name: Download Windows artifacts + uses: actions/download-artifact@v4 + with: + name: windows-bundle + path: bundle/windows + + - name: Download Linux artifacts + uses: actions/download-artifact@v4 + with: + name: linux-bundle + path: bundle/linux + + - name: Install Python dependencies + run: | + cd python-api + python -m venv venv + source venv/bin/activate + pip install -e ".[dev]" + pip install qiniu httpx + + - name: Upload to Qiniu and create release + env: + QINIU_ACCESS_KEY: ${{ secrets.QINIU_ACCESS_KEY }} + QINIU_SECRET_KEY: ${{ secrets.QINIU_SECRET_KEY }} + QINIU_BUCKET_NAME: ${{ secrets.QINIU_BUCKET_NAME }} + QINIU_BUCKET_DOMAIN: ${{ secrets.QINIU_BUCKET_DOMAIN }} + run: | + VERSION=${GITHUB_REF#refs/tags/v} + cd python-api + source venv/bin/activate + + # 上传所有平台的包 + python scripts/upload_release.py \ + --version $VERSION \ + --notes "Release $VERSION" \ + --path ../bundle +``` + +--- + +## 八、配置总结 + +### 8.1 功能配置 + +| 配置项 | 选择 | +|--------|------| +| OSS/COS 服务 | 七牛云 | +| 更新触发方式 | 启动时自动检查,需用户确认再下载 | +| 灰度发布 | 不需要 | +| 强制更新 | 需要 | +| 版本兼容性检查 | 不需要 | +| 更新检查频率 | 仅启动时 + 手动按钮 | +| 下载日志统计 | 需要 | +| macOS 安装位置 | ~/Applications(无需管理员) | +| 更新 UI 风格 | shadcn/ui 对话框 | +| 构建部署自动化 | 需要 | + +### 8.2 环境变量 + +**后端 (.env)**: +```bash +# 七牛云配置 +QINIU_ACCESS_KEY=your-access-key +QINIU_SECRET_KEY=your-secret-key +QINIU_BUCKET_NAME=meijiaka-releases +QINIU_BUCKET_DOMAIN=cdn.meijiaka.com + +# FastAPI 配置 +DATABASE_URL=postgresql+asyncpg://... +API_BASE_URL=http://localhost:8080 +``` + +**前端**: +```bash +# 应用版本(在构建时注入) +VITE_APP_VERSION=0.1.0 +``` + +### 8.3 文件清单 + +``` +python-api/ +├── app/ +│ ├── models/ +│ │ └── update.py # 数据模型 +│ ├── schemas/ +│ │ └── update.py # Pydantic schemas +│ ├── api/ +│ │ └── v1/ +│ │ └── update.py # API 路由 +│ ├── services/ +│ │ └── qiniu_service.py # 七牛云服务 +│ └── main.py # 注册路由 +├── scripts/ +│ ├── init_update_tables.sql # 数据库初始化 +│ └── upload_release.py # 上传和发布脚本 +└── .env # 环境变量 + +tauri-app/ +├── src/ +│ ├── components/ +│ │ └── UpdateDialog.tsx # 更新对话框 +│ ├── store/ +│ │ └── updateStore.ts # 更新状态管理 +│ └── pages/ +│ └── Settings.tsx # 设置页面集成 +└── src-tauri/ + ├── src/ + │ ├── updater.rs # Rust 更新模块 + │ └── lib.rs # 命令注册 + └── Cargo.toml # 依赖配置 + +.github/ +└── workflows/ + └── release.yml # 自动化发布 +``` + +--- + +## 九、测试指南 + +### 9.1 本地测试 + +1. **初始化数据库**: +```bash +cd python-api +psql -h localhost -U postgres -d meijiaka -f scripts/init_update_tables.sql +``` + +2. **创建测试版本**: +```bash +curl -X POST http://localhost:8080/api/v1/update/releases \ + -H "Content-Type: application/json" \ + -d '{ + "version": "0.1.1", + "notes": "测试版本", + "mandatory": false, + "packages": [ + { + "platform": "darwin", + "architecture": "x86_64", + "file_url": "https://cdn.meijiaka.com/releases/test.dmg", + "file_size": 102400000, + "file_hash": "sha256:test123" + } + ] + }' +``` + +3. **启动 Tauri 应用并测试更新流程** + +### 9.2 测试检查清单 + +- [ ] 版本检查 API 返回正确信息 +- [ ] 下载进度正确显示 +- [ ] 文件哈希验证工作正常 +- [ ] macOS 安装到 ~/Applications +- [ ] 安装后自动重启应用 +- [ ] 强制更新无法跳过 +- [ ] 下载日志正确记录 +- [ ] 手动检查更新按钮工作 + +--- + +## 十、常见问题 + +### Q1: 如何处理更新失败? + +**A**: Rust 后端会验证文件哈希,如果验证失败会删除损坏的文件并提示用户重试。建议实现重试机制,最多重试 3 次。 + +### Q2: 如何回滚版本? + +**A**: +1. 发布上一个版本为最新 +2. 或标记当前版本为不可用(修改 mandatory 和 notes) + +```sql +-- 回滚到上一个版本 +UPDATE app_releases +SET release_date = NOW() +WHERE version = '0.1.0'; +``` + +### Q3: 如何处理网络中断? + +**A**: Rust 下载器使用流式下载,支持断点续传。建议实现下载状态持久化,应用重启后继续下载。 + +### Q4: 如何测试更新流程? + +**A**: +1. 修改前端当前版本为旧版本 +2. 创建测试版本发布 +3. 启动应用测试更新流程 +4. 使用七牛云测试文件(不是真实安装包) + +### Q5: macOS 安装需要权限吗? + +**A**: 使用 ~/Applications 目录,用户权限即可,不需要管理员权限。 + +--- + +## 十一、错误处理与重试机制 + +### 11.1 下载重试机制 + +在 Rust 下载函数中添加重试逻辑: + +```rust +use std::time::Duration; + +#[tauri::command] +pub async fn download_update_with_retry( + app: AppHandle, + url: String, + file_hash: String, + expected_size: u64, + max_retries: u32, +) -> Result { + let mut last_error = String::new(); + + for attempt in 0..max_retries { + match download_update_internal(&app, &url, &file_hash, expected_size).await { + Ok(path) => return Ok(path), + Err(e) => { + last_error = e.clone(); + eprintln!("下载失败(尝试 {}/{}):{}", attempt + 1, max_retries, e); + + if attempt < max_retries - 1 { + // 指数退避 + let delay = Duration::from_secs(2_u64.pow(attempt)); + tokio::time::sleep(delay).await; + } + } + } + } + + Err(format!("下载失败,已重试 {} 次:{}", max_retries, last_error)) +} + +async fn download_update_internal( + app: &AppHandle, + url: &str, + file_hash: &str, + expected_size: u64, +) -> Result { + // 原有的下载逻辑 + // ... +} +``` + +### 11.2 安装失败回滚 + +macOS 安装失败时自动回滚: + +```rust +#[cfg(target_os = "macos")] +async fn install_macos(app: AppHandle, package_path: PathBuf) -> Result<(), String> { + use std::process::Command; + use std::thread; + use std::time::Duration; + + let mount_dir = PathBuf::from("/tmp/meijiaka_mount"); + let backup_path = PathBuf::from("/tmp/meijiaka_backup"); + + // 1. 挂载 DMG + if mount_dir.exists() { + let _ = Command::new("hdiutil") + .args(["detach", mount_dir.to_str().unwrap(), "-force"]) + .status(); + } + + let output = Command::new("hdiutil") + .args([ + "attach", + "-readonly", + "-nobrowse", + "-mountpoint", + mount_dir.to_str().unwrap(), + package_path.to_str().unwrap(), + ]) + .output() + .map_err(|e| format!("Failed to mount DMG: {}", e))?; + + if !output.status.success() { + return Err(format!("Failed to mount DMG: {}", + String::from_utf8_lossy(output.stderr.as_slice()))); + } + + // 2. 查找应用文件 + let app_path = find_app_in_dmg(&mount_dir)?; + let app_name = app_path.file_name() + .ok_or("Invalid app path")? + .to_string_lossy() + .to_string(); + + let dest_dir = dirs::home_dir() + .ok_or("Failed to get home directory")? + .join("Applications"); + + fs::create_dir_all(&dest_dir) + .map_err(|e| format!("Failed to create Applications dir: {}", e))?; + + let dest_path = dest_dir.join(&app_name); + + // 3. 备份旧版本 + let backup_exists = dest_path.exists(); + if backup_exists { + fs::create_dir_all(&backup_path)?; + let _ = Command::new("cp") + .args(["-R", dest_path.to_str().unwrap(), backup_path.to_str().unwrap()]) + .status(); + } + + // 4. 删除旧版本 + if dest_path.exists() { + let _ = Command::new("rm") + .args(["-rf", dest_path.to_str().unwrap()]) + .status(); + } + + // 5. 复制新版本 + let output = Command::new("cp") + .args(["-R", app_path.to_str().unwrap(), dest_dir.to_str().unwrap()]) + .output() + .map_err(|e| format!("Failed to copy app: {}", e))?; + + if !output.status.success() { + // 复制失败,恢复备份 + if backup_exists { + eprintln!("安装失败,正在恢复旧版本..."); + let _ = Command::new("cp") + .args(["-R", + backup_path.join(&app_name).to_str().unwrap(), + dest_dir.to_str().unwrap()]) + .status(); + let _ = Command::new("rm") + .args(["-rf", backup_path.to_str().unwrap()]) + .status(); + } + return Err("Failed to copy app".to_string()); + } + + // 6. 验证新版本 + if !dest_path.exists() { + // 验证失败,恢复备份 + if backup_exists { + eprintln!("验证失败,正在恢复旧版本..."); + let _ = Command::new("cp") + .args(["-R", + backup_path.join(&app_name).to_str().unwrap(), + dest_dir.to_str().unwrap()]) + .status(); + } + return Err("New app not found after installation".to_string()); + } + + // 7. 清理备份 + if backup_exists { + let _ = Command::new("rm") + .args(["-rf", backup_path.to_str().unwrap()]) + .status(); + } + + // 8. 卸载 DMG + let _ = Command::new("hdiutil") + .args(["detach", mount_dir.to_str().unwrap(), "-force"]) + .status(); + + // 9. 延迟启动新版本 + thread::spawn(move || { + thread::sleep(Duration::from_secs(2)); + let _ = Command::new("open") + .args(["-a", &app_name]) + .status(); + }); + + // 10. 退出当前应用 + app.exit(0); + + Ok(()) +} +``` + +### 11.3 下载状态持久化 + +实现下载状态保存,应用重启后继续下载: + +```rust +use serde::{Deserialize, Serialize}; +use std::fs; + +#[derive(Debug, Serialize, Deserialize)] +struct DownloadState { + url: String, + file_hash: String, + expected_size: u64, + downloaded: u64, + save_path: String, +} + +fn save_download_state(app: &AppHandle, state: &DownloadState) -> Result<(), String> { + let cache_dir = app.path().app_cache_dir()?; + let state_file = cache_dir.join("updates/download_state.json"); + + let json = serde_json::to_string_pretty(state) + .map_err(|e| format!("Failed to serialize state: {}", e))?; + + fs::write(&state_file, json) + .map_err(|e| format!("Failed to save state: {}", e)) +} + +fn load_download_state(app: &AppHandle) -> Result, String> { + let cache_dir = app.path().app_cache_dir()?; + let state_file = cache_dir.join("updates/download_state.json"); + + if !state_file.exists() { + return Ok(None); + } + + let content = fs::read_to_string(&state_file) + .map_err(|e| format!("Failed to read state: {}", e))?; + + let state: DownloadState = serde_json::from_str(&content) + .map_err(|e| format!("Failed to parse state: {}", e))?; + + Ok(Some(state)) +} +``` + +--- + +## 十二、安全性最佳实践 + +### 12.1 强制 HTTPS + +在 FastAPI 中强制使用 HTTPS: + +```python +from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware + +# 在生产环境强制 HTTPS +if not os.getenv("DEBUG"): + app.add_middleware(HTTPSRedirectMiddleware) +``` + +### 12.2 API 速率限制 + +使用 slowapi 实现速率限制: + +```python +from slowapi import Limiter, _rate_limit_exceeded_handler +from slowapi.util import get_remote_address +from slowapi.errors import RateLimitExceeded + +# 配置速率限制 +limiter = Limiter( + key_func=get_remote_address, + default_limits=["200/minute"], + storage_uri="redis://localhost:6379/1" +) + +app.state.limiter = limiter + +# 在检查更新接口添加速率限制 +@router.post("/check", response_model=UpdateInfoResponse) +@limiter.limit("10/minute") # 每分钟最多 10 次检查 +async def check_update( + request: UpdateCheckRequest, + db: AsyncSession = Depends(get_db) +): + # ... +``` + +### 12.3 文件签名验证 + +添加 HMAC 签名验证: + +```python +import hmac +import hashlib +from fastapi import Header, HTTPException + +def verify_file_signature( + file_path: str, + expected_signature: str, + secret: str +) -> bool: + """验证文件签名""" + with open(file_path, 'rb') as f: + file_hash = hashlib.sha256(f.read()).hexdigest() + + signature = hmac.new( + secret.encode(), + file_hash.encode(), + hashlib.sha256 + ).hexdigest() + + return hmac.compare_digest(signature, expected_signature) +``` + +### 12.4 CORS 配置 + +严格配置 CORS: + +```python +from fastapi.middleware.cors import CORSMiddleware + +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "tauri://localhost", # Tauri 开发环境 + "http://localhost:1420", # Vite 开发服务器 + "https://yourdomain.com", # 生产域名 + ], + allow_credentials=True, + allow_methods=["GET", "POST"], + allow_headers=["*"], +) +``` + +### 12.5 环境变量验证 + +在启动时验证必需的环境变量: + +```python +import os + +def validate_env_vars(): + """验证环境变量""" + required_vars = [ + "DATABASE_URL", + "QINIU_ACCESS_KEY", + "QINIU_SECRET_KEY", + "QINIU_BUCKET_NAME", + "QINIU_BUCKET_DOMAIN", + ] + + missing = [var for var in required_vars if not os.getenv(var)] + if missing: + raise RuntimeError(f"缺少必需的环境变量: {', '.join(missing)}") + +# 在应用启动时调用 +validate_env_vars() +``` + +### 12.6 敏感信息不记录 + +确保日志中不包含敏感信息: + +```python +import logging + +# 配置日志过滤器 +class SensitiveDataFilter(logging.Filter): + """过滤敏感数据""" + + SENSITIVE_KEYWORDS = ["access_key", "secret_key", "password", "token"] + + def filter(self, record): + msg = record.getMessage().lower() + if any(keyword in msg for keyword in self.SENSITIVE_KEYWORDS): + return False + return True + +# 添加过滤器 +logging.getLogger().addFilter(SensitiveDataFilter()) +``` + +--- + +## 十三、参考资料 + +- [Tauri 更新插件文档](https://v2.tauri.app/plugin/updater/) +- [七牛云 Python SDK](https://developer.qiniu.com/kodo/1242/python) +- [语义化版本规范](https://semver.org/lang/zh-CN/) +- [FastAPI 官方文档](https://fastapi.tiangolo.com/zh/) +- [FastAPI 安全最佳实践](https://fastapi.tiangolo.com/tutorial/security/) diff --git a/docs/database-design.md b/docs/database-design.md new file mode 100644 index 0000000..2b27bf6 --- /dev/null +++ b/docs/database-design.md @@ -0,0 +1,357 @@ +# 美家卡智影 - 数据库设计规范 + +> 企业级统一数据库命名和设计规范,遵循 "轻量云 + 全本地业务数据" 架构设计。 + +--- + +## 一、整体设计原则 + +| 原则 | 说明 | +|------|------| +| **统一前缀** | 所有业务表统一使用 `mjk_` 前缀(美家卡全称缩写)| +| **无外键设计** | 不使用数据库外键约束,数据一致性由业务层保证,架构更简洁灵活 | +| **软删除优先** | 使用 `deleted_at` 时间戳做软删除,保留历史数据便于排查 | +| **全小写下划线** | 命名全小写,单词用下划线分隔 | + +--- + +## 二、表命名规范 + +### 命名格式 + +``` +mjk_{module}_{description}[_logs] +``` + +- `mjk_` - 统一项目前缀 +- `module` - 业务模块名称 +- `description` - 内容描述 +- `_logs` 后缀 - 日志/统计类表(按事件增长) + +### 示例 + +| 表名 | 说明 | +|------|------| +| `mjk_users` | 用户账户表 | +| `mjk_model_usage_logs` | AI 模型调用日志表 | +| `mjk_avatars` | 数字人名片表(已废弃,数据迁移到本地)| +| `mjk_interface_request_logs` | 接口请求记录表 | + +--- + +## 三、字段命名规范 + +| 场景 | 规则 | 示例 | +|------|------|------| +| **主键** | 统一命名 `id`,类型 `BIGSERIAL` / `BIGINT` | `id BIGSERIAL PRIMARY KEY` | +| **外键引用** | 格式 `{referenced_table}_{primary_key}`,不要加前缀 | 引用 `mjk_users.id` → `user_id` | +| **布尔类型** | 前缀 `is_` 或 `has_` | `is_deleted`, `has_attachment` | +| **时间戳** | 后缀 `_at`,类型 `TIMESTAMP WITH TIME ZONE` | `created_at`, `updated_at`, `started_at`, `finished_at` | +| **状态字段** | 字段名固定 `status`,类型 `VARCHAR(N)`,存储枚举字符串 | `status VARCHAR(20) NOT NULL` | +| **软删除** | 字段名 `deleted_at`,允许 `NULL`,`NULL` 表示未删除 | `deleted_at TIMESTAMP WITH TIME ZONE` | + +--- + +## 四、约束与索引命名规范 + +| 对象 | 命名格式 | 示例 | +|------|---------|------| +| **主键** | `{table_name}_pkey`(PostgreSQL 默认) | `mjk_interface_request_logs_pkey` | +| **唯一约束** | `uk_{table_name}_{column_list}` | `uk_mjk_interface_request_logs_request_id` | +| **普通索引** | `idx_{table_name}_{column_list}` | `idx_mjk_interface_request_logs_user_id` | + +--- + +## 五、所有业务表结构 + +--- + +### 1. `mjk_users` - 用户基本信息表 + +存储用户基本认证信息,云端只存账户,不存业务数据。 + +```sql +CREATE TABLE mjk_users ( + id BIGSERIAL PRIMARY KEY, + mobile VARCHAR(20) UNIQUE NOT NULL, + nickname VARCHAR(64), + avatar_url TEXT, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- 索引 +CREATE UNIQUE INDEX idx_mjk_users_mobile ON mjk_users(mobile); +``` + +**字段说明**: +| 字段 | 说明 | +|------|------| +| `id` | 用户唯一ID | +| `mobile` | 手机号(登录账号,唯一)| +| `nickname` | 用户昵称 | +| `avatar_url` | 头像URL | +| `created_at` | 创建时间 | +| `updated_at` | 最后更新时间 | + +--- + +### 2. `mjk_user_credits` - 用户积分账户记录表 + +记录用户积分账户的所有变动(充值、消费),每个变动一条记录。 + +```sql +CREATE TABLE mjk_user_credits ( + id BIGSERIAL PRIMARY KEY, + user_id VARCHAR(50) NOT NULL, + change_type VARCHAR(20) NOT NULL, -- recharge / consume + change_credits INTEGER NOT NULL, -- 变动积分数(充值正,消费负) + balance_before INTEGER NOT NULL, -- 变动前余额 + balance_after INTEGER NOT NULL, -- 变动后余额 + interface_type VARCHAR(50), -- 消费接口类型(消费时才有) + request_id VARCHAR(64), -- 关联接口请求ID + remark VARCHAR(200), -- 备注(充值订单号等) + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- 索引 +CREATE INDEX idx_mjk_user_credits_user_id ON mjk_user_credits(user_id); +CREATE INDEX idx_mjk_user_credits_created_at ON mjk_user_credits(created_at); +CREATE INDEX idx_mjk_user_credits_change_type ON mjk_user_credits(change_type); +``` + +**字段说明**: +| 字段 | 说明 | +|------|------| +| `id` | 记录ID | +| `user_id` | 关联用户ID | +| `change_type` | 变动类型:`recharge`(充值) / `consume`(消费) | +| `change_credits` | 变动积分,充值为正,消费为负 | +| `balance_before` | 变动前积分余额 | +| `balance_after` | 变动后积分余额 | +| `interface_type` | 消费接口类型(仅消费时有)| +| `request_id` | 关联接口请求ID(可用于追溯)| +| `remark` | 备注,充值时存订单号 | +| `created_at` | 变动时间 | + +**余额计算**:用户当前余额 = `sum(change_credits)`,可以随时计算,也可以在用户表存冗余字段加速查询。 + +--- + +### 3. `mjk_model_usage_logs` - AI 模型调用日志表 + +记录每一次 AI 模型调用,用于成本统计和监控。 + +```sql +CREATE TABLE mjk_model_usage_logs ( + id BIGSERIAL PRIMARY KEY, + model_id VARCHAR(100) NOT NULL, + platform_id VARCHAR(50) NOT NULL, + task_type VARCHAR(50) NOT NULL, + prompt_tokens INTEGER NOT NULL DEFAULT 0, + completion_tokens INTEGER NOT NULL DEFAULT 0, + total_tokens INTEGER NOT NULL DEFAULT 0, + cost_cny FLOAT NOT NULL DEFAULT 0.0, + response_time_ms INTEGER, + success BOOLEAN NOT NULL DEFAULT TRUE, + error_message TEXT, + user_id VARCHAR(50), + project_id VARCHAR(50), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- 索引 +CREATE INDEX idx_mjk_model_usage_logs_user_id ON mjk_model_usage_logs(user_id); +CREATE INDEX idx_mjk_model_usage_logs_created_at ON mjk_model_usage_logs(created_at); +``` + +**字段说明**: +- `model_id` - AI 模型ID +- `platform_id` - AI 平台ID(openai/volcengine/klingai 等) +- `task_type` - 任务类型(script/polish/chat 等) +- `prompt_tokens` - 输入 Token 数 +- `completion_tokens` - 输出 Token 数 +- `total_tokens` - 总 Token 数 +- `cost_cny` - 消耗金额(人民币元) +- `response_time_ms` - 响应时间(毫秒) +- `success` - 是否成功 +- `error_message` - 错误信息 +- `user_id` - 关联用户ID +- `project_id` - 关联项目ID +- `created_at` - 创建时间 + +--- + +### 4. `mjk_interface_request_logs` - 接口请求记录表(新增) + +**按后端接口类型记录所有用户请求,统计积分消耗**。这张表是顶层的接口请求统计,每一次前端调用后端接口都记一条。 + +```sql +CREATE TABLE mjk_interface_request_logs ( + id BIGSERIAL PRIMARY KEY, + request_id VARCHAR(64) NOT NULL, + user_id VARCHAR(50) NOT NULL, + interface_type VARCHAR(50) NOT NULL, + interface_name VARCHAR(100), + status VARCHAR(20) NOT NULL, + cost_credits INTEGER NOT NULL DEFAULT 0, + started_at TIMESTAMP WITH TIME ZONE NOT NULL, + finished_at TIMESTAMP WITH TIME ZONE, + error_message TEXT, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- 唯一约束 +ALTER TABLE mjk_interface_request_logs + ADD CONSTRAINT uk_mjk_interface_request_logs_request_id + UNIQUE (request_id); + +-- 索引 +CREATE INDEX idx_mjk_interface_request_logs_user_id + ON mjk_interface_request_logs(user_id); +CREATE INDEX idx_mjk_interface_request_logs_interface_type + ON mjk_interface_request_logs(interface_type); +CREATE INDEX idx_mjk_interface_request_logs_status + ON mjk_interface_request_logs(status); +CREATE INDEX idx_mjk_interface_request_logs_created_at + ON mjk_interface_request_logs(created_at); +``` + +**字段说明**: +| 字段 | 说明 | +|------|------| +| `id` | 日志记录自增ID | +| `request_id` | 本次请求唯一ID(全局唯一)| +| `user_id` | 请求用户ID | +| `interface_type` | 接口类型(枚举见下方)| +| `interface_name` | 接口名称(可读描述)| +| `status` | 请求状态:`success` / `failed` | +| `cost_credits` | 消耗积分数 | +| `started_at` | 请求开始时间 | +| `finished_at` | 请求结束时间 | +| `error_message` | 失败原因 | +| `created_at` | 记录创建时间 | + +**`interface_type` 枚举值**: + +| 值 | 说明 | +|----|------| +| `script_generate` | 脚本生成 | +| `script_polish` | 脚本润色 | +| `avatar_clone` | 数字人克隆 | +| `video_generate` | 数字人视频生成 | +| `subtitle_generate` | 字幕打轴生成 | +| `image_generate` | 封面图片生成 | + +--- + +### 5. `mjk_avatars` - 数字人名片表(已废弃) + +> **迁移计划**:原 `avatars` 表已废弃,所有数字人元数据全量迁移到用户本地存储。 +> 路径:`~/Documents/Meijiaka/avatars/{avatar_id}/meta.json` + +保留本表仅用于存量数据兼容,后续可删除。 + +```sql +CREATE TABLE mjk_avatars ( + id VARCHAR(64) PRIMARY KEY, + user_id VARCHAR(50) NOT NULL, + name VARCHAR(64) NOT NULL, + voice_id VARCHAR(64), + element_id BIGINT, + voice_task_id VARCHAR(128), + element_task_id VARCHAR(128), + video_url TEXT NOT NULL, + trial_url TEXT, + status VARCHAR(32) NOT NULL DEFAULT 'pending', + fail_reason TEXT, + deleted_at TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL +); + +-- 索引 +CREATE INDEX idx_mjk_avatars_user_id ON mjk_avatars(user_id); +CREATE INDEX idx_mjk_avatars_voice_task_id ON mjk_avatars(voice_task_id); +CREATE INDEX idx_mjk_avatars_element_task_id ON mjk_avatars(element_task_id); +``` + +--- + +## 六、本地存储结构(业务数据) + +所有业务数据(项目、脚本、数字人)都存在用户本地磁盘,云端只存储日志和统计: + +``` +~/Documents/Meijiaka/ +├── config.json # 全局应用配置 +├── projects/ +│ └── {project_id}/ +│ ├── meta.json # 项目元数据 +│ ├── segments.json # 脚本/分镜数据 +│ └── assets/ # 媒体文件 +├── avatars/ +│ └── {avatar_id}/ +│ ├── meta.json # 数字人元数据(id/name/voice_id/element_id/status 等) +│ └── source.mp4 # 原始上传视频 +└── cache/ # 临时文件 +``` + +**`avatars/{avatar_id}/meta.json` 结构**: + +```json +{ + "id": "avt_xxx", + "name": "我的数字人", + "voiceId": "kling-voice-id", + "elementId": 12345678, + "voiceTaskId": "kling-task-id", + "elementTaskId": "kling-task-id", + "videoUrl": "https://.../source.mp4", + "trialUrl": "https://.../trial.wav", + "status": "succeed", + "failReason": null, + "createdAt": "2026-04-16T10:00:00Z", + "updatedAt": "2026-04-16T10:05:00Z" +} +``` + +--- + +## 七、架构总结 + +| 数据类型 | 存储位置 | 说明 | +|---------|----------|------| +| 用户基本信息 | 云端 `mjk_users` | 必须存云端 | +| 用户积分变动记录 | 云端 `mjk_user_credits` | 记录充值/消费流水,统计用户余额 | +| AI 模型调用日志 | 云端 `mjk_model_usage_logs` | AI 模型细粒度调用日志(成本统计)| +| 接口请求记录 | 云端 `mjk_interface_request_logs` | 按后端接口记录请求、状态、消耗积分 | +| 项目/脚本/分镜 | 用户本地 JSON | 全本地业务数据 | +| 数字人元数据/原始视频 | 用户本地文件 | 全本地业务数据(原云端表已废弃)| +| 合成输出视频 | 用户本地文件 | 全本地 | + +完美符合设计理念:**轻量云账号 + 全本地业务数据**。 + +--- + +## 八、迁移说明 + +### 从无前缀版本迁移到统一前缀版本 + +1. 使用 Alembic 自动重命名所有现有表 + ```sql + ALTER TABLE users RENAME TO mjk_users; + ALTER TABLE model_usage_logs RENAME TO mjk_model_usage_logs; + ALTER TABLE avatars RENAME TO mjk_avatars; + ``` +2. 新建两张表: + - `mjk_user_credits` - 用户积分变动记录表 + - `mjk_interface_request_logs` - 接口请求记录表 +3. 修改所有 SQLAlchemy 模型中的 `__tablename__` +4. 后续:将 `mjk_avatars` 数据迁移到用户本地后可删除该表 + +--- + +*版本:v1.1* +*创建日期:2026-04-16* +*更新:新增 `mjk_user_credits` 积分账户表* diff --git a/docs/kling-api-dev.md b/docs/kling-api-dev.md new file mode 100644 index 0000000..dac6931 --- /dev/null +++ b/docs/kling-api-dev.md @@ -0,0 +1,4282 @@ +# 可灵AI (Kling) 新系统 API 开发规范及参照标准 + +> 本文档基于「可灵AI」新系统 API 接口文档整理 +> +> 调用域名:`https://api-beijing.klingai.com` +> +> 最后更新:2026年4月 + +--- + +「可灵AI」新系统 API 接口文档 + +注意: +新系统调用域名已由 https://api.klingai.com 变更为 https://api-beijing.klingai.com。(更新于2025年6月30日) +| 更新时间 | 更新说明 | +|---|---| +| 2026.04.01 | 【视频生成:3.0-Omni】细化参考视频对视频角色主体、多图主体和参考图数量的影响 | +详见image_list参数和element_list参数的参数说明 +本次更新仅细化说明,与原有逻辑一致 +| 2026.03.23 | 【通用】多图主体也可绑定音色 | +|---|---| +此前仅视频角色主体支持绑定音色 +多图主体绑定音色方式与视频角色主体一致,通过element_voice_id传参即可 +| 2026.03.18 | 【视频生成-视频特效】新增特效 | +|---|---| +新增6款特效:“关门,挂挡!”、“闭嘴!我的梦”、“法式优雅”、“手指滑滑变装”、“花神驾到”、“丝滑转场” +特效内容详见:特效模版中心 +| 2026.03.11 | 【视频生成:3.0-Omni】支持智能分镜 | +|---|---| +当shot_type参数值设为intelligence时,可实现智能分镜 +【视频生成】细化部分业务逻辑说明 +使用kling-video-o1模型生成首尾帧视频时,不支持引用主体 +通过视频定制主体,仅支持定制写实风格、人形主体 +创建主体 API 实例代码中 element_voice_id 参数格式为 string +多镜头不支持首尾帧 +非首尾帧图片可不传type参数 +音色控制功能支持模型范围说明 +| 2026.03.04 | 【视频生成-动作控制】V3.0 全新上线 | +|---|---| +可通过绑定主体提升主体一致性,绑定主体时只能参考视频中的人物朝向 +增加model_name参数区分模型版本,默认kling-v2-6 +生成标准模式动作控制视频,每秒扣减0.9积分;生成高品质模式动作控制视频,每秒扣减1.2积分 +| 2026.02.27 | 【视频生成-视频特效】新增特效 | +|---|---| +新增1款特效:被窝有“诈” +特效内容详见:特效模版中心 +| 2026.02.25 | 【视频生成】3.0 Omni、V3模型上线 | +|---|---| +【图像生成】3.0 Omni、V3模型上线 +【通用】主体相关功能全新升级 +支持通过视频创建主体,同时创建主体时可绑定音色并用于生成视频时指定音色。 +创建主体升级为异步服务,满足更多主体相关功能。 +新主体服务采用全新API(advanced-custom-elements),原有API可正常使用,但主体库相对独立,无法跨API查询。 +| 2026.02.11 | 【视频生成-视频特效】新增特效 | +|---|---| +新增9款特效:“八方来财舞”、“弥渡山歌”、“上花轿”、“好运舞 2”、“来财舞”、“雪夜之吻”、“永恒之吻”、“马力全开舞”和“秧歌舞” +特效内容详见:特效模版中心 +| 2026.02.04 | 【视频生成-视频特效】新增特效 | +|---|---| +新增8款特效:“你的专属烟花”、“掌心小人”、“手搓颜料变装”、“蹴鞠闹元宵”、“汤圆:我摊牌了”等 +特效内容详见:特效模版中心 +| 2026.02.03 | 【视频生成-视频特效】新增特效 | +|---|---| +新增9款特效:“嘻哈炫舞 2”、“鸽子舞”、“甜心舞 1”、“回村前后”等 +特效内容详见:特效模版中心 +| 2026.01.27 | 【视频生成-视频特效】新增特效 | +|---|---| +新增7款特效:“看看我家长龙宴”、“宠物出游、“冰雪奇迹”、“横移分身转场”、“马年烟花”、“辞旧迎新”等 +特效内容详见:特效模版中心 +| 2026.01.23 | 【通用】功能上新:支持智能补全主体不同角度图片 | +|---|---| +可通过主体正面图,自动推理出该主体其他角度图片,每次可生成3组结果供选择 +按服务访问次数计费,每次扣减0.5积分 +【通用】功能上新:查询任务结果时,可直接获取当前任务所消耗积分结果 +对应参数key为:final_unit_deduction +【通用】功能上新:生成图片或视频时,可同时生成含水印结果 +入参时将watermark_info中的enabled的参数设为true时,即可同时生成含水印结果 +返回结果的watermark_url参数为含水印版本 +目前仅部分API支持水印,详见当前API文档 +| 2026.01.16 | 【视频生成-视频特效】新增特效 | +|---|---| +新增31款特效:“醉酒舞”、“刀马舞、“舞狮贺岁”、“咚咚咚,红包来了”、“怪盗珠宝”、“镜头拉远”等 +特效内容详见:特效模版中心 +| 2025.12.22 | 【视频生成】动作控制功能全新上线 | +|---|---| +请注意,为保障生成效果,当前动作控制功能对输入的参考图和参考视频有较为严格的检测机制,检测失败无法生成(API不扣费),请参考【使用指南】中对图像和视频的要求进行使用 + +* + +基于新API路径实现,需同时上传参考图像和参考视频 +支持标准模式和高品质模式,生成标准模式视频时每秒扣减0.5积分,生成高品质模式视频时每秒扣减0.8积分,秒数四舍五入取整 +参考视频时长上限与所生成视频人物朝向相关:与参考视频一致时可达30秒,与参考图像一致时仅支持10秒 +暂不支持动作库 +| 2025.12.18 | 【视频生成-视频特效】新增特效 | +|---|---| +新增16款特效: “和他/她跨年”、“我的跨年分会场”、“下一秒圣诞”、“生日主角”等 +通知:“亲吻”、“打架”、“拥抱”、“给你点赞”、“老虎拥抱”、“养只狮子”、“3D卡通1” 特效将于2026.1.30 日下线 +特效内容详见:特效模版中心 +| 2025.12.16 | 【视频生成】V2.6模型能力升级,支持指定音色 | +|---|---| +通过prompt和voice_list实现,指定音色生视频:5s扣减6积分,10s扣减12积分 +支持音色定制,也可使用系统预置音色 +| 2025.12.15 | 【视频生成】V2.6模型上线 | +|---|---| +支持“文生视频”和“图生视频” +通过sound参数控制生成视频时是否包含同时生成配音 +【视频生成】Omni-Video模型上线 +全新API,仅通过提示词即可实现多种能力 +【图像生成】Omni-Image模型上线 +全新API,仅通过提示词即可实现多种能力 +| 2025.12.11 | 【视频生成-视频特效】新增特效 | +|---|---| +新增20款圣诞、新年、冬日主题特效: “2026 绽放时刻”、“圣诞惊喜礼盒”、“雪地童话”等 +通知:“一起来庆生”和“C4D卡通”特效将于2025.12.30 日下线 +特效内容详见:特效模版中心 +| 2025.12.04 | 【数字人】能力升级,支持生成5分钟数字人视频 | +|---|---| +无感升级,无需修改接口参数 +| 2025.11.25 | 【视频生成-视频特效】新增特效 | +|---|---| +新增10款单图特效: “感恩节气球游行”、“跳跳姜饼人”、“子弹时间”等 +特效内容详见:特效模版中心 +| 2025.11.19 | 【视频生成-视频特效】新增特效 | +|---|---| +新增7款单图特效: “光之精灵”、“测测你的守护神”、“单板滑雪”等 +特效内容详见:特效模版中心 +【多图参考生图】支持V2.1模型 +生成1张图片扣减16积分 +| 2025.11.17 | 【图生视频】V2.5-Turbo PRO支持首尾帧 | +|---|---| +同时传入image参数值和image_tail参数值即可实现 +生成5s视频扣减2.5积分,生成10s视频扣减5积分 +| 2025.11.11 | 【视频生成】V2.5-Turbo支持STD模式 | +|---|---| +“文生视频”和“图生视频”均已支持 +生成5s视频扣减1.5积分,生成10s视频扣减3积分 +| 2025.11.3 | 【数字人】视频生成效率提升,原小时级耗时压缩至10分钟+ | +|---|---| +无需修改任何代码 +| 2025.10.27 | 【视频生成-视频特效】新增特效 | +|---|---| +新增10款万圣节特效: “南瓜人变身”、“可爱幽灵变身”、“门外是谁-万圣节”、“万圣大逃亡”等 +特效内容详见:特效模版中心 +| 2025.10.20 | 【视频生成】文生视频和图生视频支持V2.5-turbo模型 | +|---|---| +支持高品质版,生成5s视频低至2.5积分 +【通用】能力上新:推出图片元素识别API,可用于多图参考生视频、多模态视频编辑功能 +可识别主体、面部、服装等,一次请求可获得4组结果(如有) +| 2025.10.15 | 【视频生成-视频特效】新增特效 | +|---|---| +新增8款单图特效: “内心真实想法”、“蹦床”、“下一秒发生什么”等 +特效内容详见:特效模版中心 +【数字人】全新功能上线 +基于图片与音频或文本生成动作表情自然、韵律与人声一致的视频 +如果输入图片中有多个人脸,暂不支持对人脸做指定 +| 2025.9.28 | 特别提醒:为保障信息安全,所有接口生成的图片/视频会在30天后被清理,为了避免影响使用,请您在生成后及时转存 | +|---|---| +| 2025.9.28 | 【视频生成-视频特效】新增 1 款特效 | +新增 1款单图特效: “万物皆可吃月饼” +特效内容详见:特效模版中心 +| 2025.9.26 | 【视频生成-视频特效】新增 3 款特效 | +|---|---| +新增 3款单图特效: “狂暴金刚”、“一飞冲天”、“洗刷刷洗刷刷” +特效内容详见:特效模版中心 +【语音合成】功能优化:支持合成1000长度内容的音频 +| 2025.9.15 | 【对口型】能力更新:支持多人画面对口型、开始对口型时间 | +|---|---| +通过face_id指定说话人,通过sound_insert_time指定开始对口型时间 +支持裁剪音频 +【语音合成】全新上线:上线文本转播报音,可实现试听功能 +可同时生成audio_id,可用于可灵任意API +| 2025.9.12 | 【视频生成-视频特效】新增 9 款特效 | +|---|---| +新增 9 款单图特效: “呼叫转移”、“捏一捏”等; +特效内容详见:特效模版中心 +| 2025.9.11 | 【对口型】能力更新:支持多人画面对口型、开始对口型时间 | +|---|---| +通过face_id指定说话人,通过sound_insert_time指定开始对口型时间 +支持裁剪音频 +【语音合成】全新上线:上线文本转播报音,可实现试听功能 +可同时生成audio_id,可用于可灵任意API +| 2025.9.5 | 【视频生成】能力更新:V2.1模型支持首尾帧 | +|---|---| +可生成5s或10s的视频,暂时仅支持高品质模式 +【视频生成】功能优化:视频生音效 +支持用户自行输入音效提示词、配乐提示词,以及开启ASMR模式 +| 2025.9.1 | 【视频生成-视频特效】新增 5 款特效 | +|---|---| +新增 5 款单图特效: “萌宠京剧”、“肌肉觉醒”等; +特效内容详见:特效模版中心 +| 2025.8.19 | 【视频生成-视频特效】新增 63 款特效 | +|---|---| +新增 62 款单图特效,1 款双人互动特效,累计 80 款特效可支持调用; +新增「特效模版中心」页面,支持查看特效详细信息与调用价格:特效模版中心 +| 2025.8.15 | 【视频生成】功能优化:视频生音效 | +|---|---| +视频音效生成支持​​全分辨率​​视频上传 +【多图参考生视频】功能优化:效果比上一版本提升 102% +主体一致性、动态质量、互动自然度等维度明显提升。 +无感升级,不需修改代码。 +【图像生成】模型更新:上线新V2.0模型,支持近300种风格 +参数示例:"model_name": "kling-v2-new" +| 2025.8.12 | 【视频生成】能力更新:文生视频支持V1.6 PRO | +|---|---| +参数示例:"mode": "pro" +可生成5s和10s的视频 +| 2025.8.1 | 【视频生成】新增能力:文生音效 | +|---|---| +支持通过输入文本描述(prompt)生成音效 +【视频生成】新增能力:视频生音效 +支持对所有可灵模型生成的视频,进行视频配音 +支持对用户自行上传的视频,进行视频配音 +| 2025.7.30 | 【图像生成】支持V2.1模型 | +|---|---| +文生图支持kling-v2-1模型 +【多图参考生图】全新上线 +支持主体参考subject_image_list、背景参考scene_image、风格参考style_image +单价0.4元,每生成1张图片从资源包总数里扣减16 +仅支持kling-v2模型 +| 2025.7.21 | 【视频生成-视频特效】新增单图特效 | +|---|---| +新增「单图特效」:7款,“果冻液压机jelly_press”、“果冻切一切jelly_slice”、“果冻捏一捏jelly_squish”、“果冻摇一摇jelly_jiggle”、“像素世界pixelpixel”、“美式证件照yearbook”、“一键拍立得instant_film” +包括创建任务、查询任务(单个)、查询任务(列表)接口 +【多模态视频编辑】全新上线 +支持对已有视频增加元素(addition)、替换元素(swap)、删除元素(removal) +使用时,需先初始化视频并对视频进行标记,再执行创建任务等操作 +| | | +|---|---| +| 2025.7.7 | 【视频生成-视频特效】新增单图特效 | +新增「单图特效」:2款,“一键变手办anime_figure”、“一飞冲天rocketrocket” +包括创建任务、查询任务(单个)、查询任务(列表)接口 +| 2025.6.30 | 【对口型】功能升级 | +|---|---| +支持视频时长上限从10秒增加至60秒 +生成视频耗时低至2分钟 +无需改造代码 +| 2025.6.19 | 【视频生成】支持V2.1模型 | +|---|---| +上线图生视频 V2.1 标准版,支持标准模式(STD)和高品质版(PRO) +上线图生视频 V2.1 大师版(Master) +上线文生视频 V2.1 大师版(Master) +| 2025.6.6 | 【图像生成】支持V2.0图生图模型,文生图模型支持选择分辨率(1K, 2K) | +|---|---| +【图像生成】支持扩图 +| 2025.5.13 | 【图像生成】支持V2.0模型 | +|---|---| +支持V2.0文生图模型 +【视频生成】支持V2.0模型 +支持V2.0文生视频模型、图生视频模型 +V2.0暂不支持mode参数 +【多图参考生视频】全新上线 +最多支持从4张图片中选取主体 +支持自定义生成视频的长宽比:16:9,9:16,1:1 +| 2025.4.25 | 【视频生成-视频特效】新增单图特效 | +|---|---| +新增「单图特效」:2款,“花花世界bloombloom”、“魔力转圈圈dizzydizzy” +包括创建任务、查询任务(单个)、查询任务(列表)接口 +| 2025.3.31 | 【视频生成】V1.6模型支持仅尾帧生成视频 | +|---|---| +可通过V1.6 高品质模型基于图片生成图片前几秒的视频画面 +【视频生成】V1.5模型、V1.6模型支持视频延长 +可基于V1.5模型和V1.6模型生成的视频,续写之后4~5秒的内容 +如果是用“仅尾帧”生成的视频,则续写之前4~5秒的内容 +| 2025.3.25 | 【图像生成】V1.5模型支持角色特征参考和人物长相参考 | +|---|---| +角色特征参考:通过文本描述即可随意改变人物的服装、发型、配饰、场景等元素,且可保持人物长相与参考图高度相似,轻易实现单人物多场景的创作需求 +人物长相参考:适用于人物和常见动物角色,可控信息由长相扩大到主体,同时支持用户分别调节长相和主体的相似强度,通过文本描述,可以将角色置于任何场景,为用户在创作阶段提供单角色多镜头多场景的稳定素材支持 +| 2025.3.12 | 【视频生成-视频特效】新增单图特效 | +|---|---| +开放「单图特效」:3款,“快来惹毛我fuzzyfuzzy”、“捏捏乐squish”与“万物膨胀expansion” +包括创建任务、查询任务(单个)、查询任务(列表)接口 +【视频生成】新模型支持首尾帧、仅尾帧、动态笔刷、运镜控制 +V1.5支持首尾帧、仅尾帧、动态笔刷、运镜控制 +V1.6支持首尾帧 +【视频生成】对口型支持自定义视频,支持更多可用音色 +支持为任意1080p或720p、10s内视频对口型 +新增8个中、英文音色可直接用于给对口型视频配音 +【图像生成】支持V1.5模型 +画面美感提升:构图与光影更加协调,尤其是人像美观度大幅提升,呈现更高级的美学效果 +画面质量提升:增强了画面细节表现,色彩还原更加自然,层次感更加丰富 +长宽比支持支持21:9 +| 2025.3.5 | 【视频生成】新增能力:视频创意特效 | +|---|---| +开放「双人互动特效」:3款,“拥抱hug”、亲吻kiss”、比心heart_gesture” +包括创建任务、查询任务(单个)、查询任务(列表)接口 +相比通用的视频生成接口,视频特效接口开放了更灵活的调用参数、封装了特效场景所需的前后处理能力(例如双人特效,支持传入两张人像图、并完成两张人像图的自动拼接,用拼接后的整图进行视频生成),调用更方便快捷 +| 2025.2.14 | 【图像生成】model字段变更 | +|---|---| +请您注意,为了保持命名统一,原 model字段变更为 model_name字段,未来请您使用该字段来指定需要调用的模型版本。 +同时,我们保持了行为上的向前兼容,如您继续使用原 model字段,不会对接口调用有任何影响、不会有任何异常,等价于 model_name为空时的默认行为(即调用V1模型) +| 2025.1.7 | 【视频生成】V1.6模型正式上线 | +|---|---| +支持文生视频标准模式(STD),图生视频标准模式(STD)和高品质模式(PRO) +暂不支持尾帧和运动笔刷、运镜等控制类功能 +请您注意,为了保持命名统一,原 model字段变更为 model_name字段,未来请您使用该字段来指定需要调用的模型版本。 +同时,我们保持了行为上的向前兼容,如您继续使用原 model字段,不会对接口调用有任何影响、不会有任何异常,等价于 model_name为空时的默认行为(即调用V1模型) +| 2024.12.30 | 【虚拟试穿】新增V1.5模型 | +|---|---| +V1.5模型是V1.0模型的全面升级版本 +V1.5模型支持单个服装(上装upper、下装lower、与连体装dress)试穿,以及“上装+下装”形式服装的组合试穿 +| 2024.12.23 | 【视频生成】新增能力:对口型 | +|---|---| +可灵 1.0 模型、可灵 1.5 模型生成的视频,只要满足视频画面的人脸条件,均支持对口型 +包括创建任务、查询任务(单个)、查询任务(列表)接口 +| 2024.12.9 | 【视频生成】V1.5模型,正式开放标准模式(STD)调用,支持视频生成 - 图生视频,暂不支持文生视频 | +|---|---| +支持标准模式 +不支持尾帧控制 +其他参数均支持 +请您注意,为了保持命名统一,原 model字段变更为 model_name字段,未来请您使用该字段来指定需要调用的模型版本。 +同时,我们保持了行为上的向前兼容,如您继续使用原 model字段,不会对接口调用有任何影响、不会有任何异常,等价于 model_name为空时的默认行为(即调用V1模型) +| 2024.12.2 | 【视频生成】能力地图 | +|---|---| +由于视频生成模型有多个模型版本(V1,V1.5),且有多种插件能力(镜头控制/首尾帧/运动笔刷/续写...),为了方便大家更直观的查询不同版本、不同能力的开放情况,我们制作了“能力地图”方便大家查阅(详见“3-0能力地图”) +| 2024.11.29 | 【视频生成 - 图生视频】新增运动笔刷 | +|---|---| +仅支持V1.0模型的标准模式 5s 与高品质模式 5s,V1.5模型暂不支持 +| 2024.11.15 | 【视频生成】V1.5模型,正式开放高品质模式(PRO)调用,支持视频生成 - 图生视频,暂不支持文生视频 | +|---|---| +仅支持高品质模式 +不支持尾帧控制 +其他参数均支持 +【视频生成】新增能力:视频延长 +支持对V1.0模型生成的视频直接进行延长,每次增加4-5s的视频时长 +包括创建任务、查询任务(单个)、查询任务(列表)接口 +【视频生成】其他 +新增“external_task_id”字段,您可以在创建任务时自定义任务id,查询时也可以通过该自定义id查询视频 +请您注意,为了保持命名统一,原 model字段变更为 model_name字段,未来请您使用该字段来指定需要调用的模型版本。 +同时,我们保持了行为上的向前兼容,如您继续使用原 model字段,不会对接口调用有任何影响、不会有任何异常,等价于 model_name为空时的默认行为(即调用V1模型) +| 2024.10.30 | 新增“查询资源包列表及余量”接口,方便您自主查询,见“六、账号信息查询” | +|---|---| +| 2024.10.25 | 增加对于模型生成物(图片/视频)存储时长的说明 | +为保障信息安全,生成的图片/视频会在30天后被清理,辛苦大家及时转存 +| 2024.10.15 | 增加生成鉴权信息的Java示例代码 | +|---|---| +| 2024.9.19 | 视频生成相关API | +创建任务时,请求参数里的正向提示词(prompt)和负向提示词(negative_prompt),字符数限制更新为:不超过2500个字符 +| 2024.9.19 | 正式支持“AI虚拟试穿”相关API(kolors-virtual-try-on) | +|---|---| + + +一、通用信息 +调用域名 +https://api-beijing.klingai.com +⚠️注意:新系统调用域名已由 https://api.klingai.com 变更为 https://api-beijing.klingai.com。此域名适用于服务器在中国地区的用户。 + +接口鉴权 +Step-1:获取 AccessKey + SecretKey +Step-2:您每次请求API的时候,需要按照固定加密方法生成API Token +加密方法:遵循JWT(Json Web Token, RFC 7519)标准 +JWT由三个部分组成:Header、Payload、Signature +示例代码(Python): + +示例代码(Java): + +Step-3:用第二步生成的API Token组装成Authorization,填写到 Request Header 里 +组装方式:Authorization = "Bearer XXX", 其中XXX填写第二步生成的API Token(注意Bearer跟XXX之间有空格) + +错误码 +| HTTP状态码 | 业务码 | 业务码定义 | 业务码解释 | 建议解决方案 | +|---|---|---|---|---| +| 200 | 0 | 请求成功 | - | - | +| 401 | 1000 | 身份验证失败 | 身份验证失败 | 检查Authorization是否正确 | +| 401 | 1001 | 身份验证失败 | Authorization为空 | 在Request Header中填写正确的Authorization | +| 401 | 1002 | 身份验证失败 | Authorization值非法 | 在Request Header中填写正确的Authorization | +| 401 | 1003 | 身份验证失败 | Authorization未到有效时间 | 检查token的开始生效时间,等待生效或重新签发 | +| 401 | 1004 | 身份验证失败 | Authorization已失效 | 检查token的有效期,重新签发 | +| 429 | 1100 | 账户异常 | 账户异常 | 检查账户配置信息 | +| 429 | 1101 | 账户异常 | 账户欠费(后付费场景) | 进行账户充值,确保余额充足 | +| 429 | 1102 | 账户异常 | 资源包已用完/已过期(预付费场景) | 购买额外的资源包,或开通后付费服务(如有) | +| 403 | 1103 | 账户异常 | 请求的资源无权限,如接口/模型 | 检查账户权限 | +| 400 | 1200 | 请求参数非法 | 请求参数非法 | 检查请求参数是否正确 | +| 400 | 1201 | 请求参数非法 | 参数非法,如key写错或value非法 | 参考返回体中message字段的具体信息,修改请求参数 | +| 404 | 1202 | 请求参数非法 | 请求的method无效 | 查看接口文档,使用正确的request method | +| 404 | 1203 | 请求参数非法 | 请求的资源不存在,如模型 | 参考返回体中message字段的具体信息,修改请求参数 | +| 400 | 1300 | 触发策略 | 触发平台策略 | 检查是否触发平台策略 | +| 400 | 1301 | 触发策略 | 触发平台的内容安全策略 | 检查输入内容,修改后重新发起请求 | +| 429 | 1302 | 触发策略 | API请求过快,超过平台速率限制 | 降低请求频率、稍后重试,或联系客服增加限额 | +| 429 | 1303 | 触发策略 | 并发或QPS超出预付费资源包限制 | 降低请求频率、稍后重试,或联系客服增加限额 | +| 429 | 1304 | 触发策略 | 触发平台的IP白名单策略 | 联系客服 | +| 500 | 5000 | 内部错误 | 服务器内部错误 | 稍后重试,或联系客服 | +| 503 | 5001 | 内部错误 | 服务器暂时不可用,通常是在维护 | 稍后重试,或联系客服 | +| 504 | 5002 | 内部错误 | 服务器内部超时,通常是发生积压 | 稍后重试,或联系客服 | + + +二、图像生成 +2-0 能力地图 +| kling-image-o1 | | 自定义长宽比(1K/2K) | 智能长宽比 | +|---|---|---|---| +| 文生图 | 单图生成 | ✅ | - | +| | 其他 | - | - | +| 图生图 | 单图生成 | ✅ | ✅ | +| | 主体控制 | | | +(仅多图主体) +✅ +✅ +| | 其他 | - | - | | +|---|---|---|---|---| +| | kling-v3-omni | | 自定义长宽比(1K/2K/4K) | 智能长宽比 | +| 文生图 | 单图生成 | ✅ | ✅ | | +| | 其他 | - | - | | +| 图生图 | 单图生成 | ✅ | ✅ | | +| | 组图生成 | ✅ | ✅ | | +| | 主体控制 | | | | +(仅多图主体) +✅ +✅ +| | 其他 | - | - | | | | | | | | +|---|---|---|---|---|---|---|---|---|---|---| +| | kling-v1 | | 1:1 | 16:9 | 4:3 | 3:2 | 2:3 | 3:4 | 9:16 | 21:9 | +| 文生图 | - | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | - | | +| 图生图 | 通用垫图 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | - | | +| | 其他能力 | - | - | - | - | - | - | - | - | | +| | kling-v1-5 | | 1:1 | 16:9 | 4:3 | 3:2 | 2:3 | 3:4 | 9:16 | 21:9 | +| 文生图 | - | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | +| 图生图 | 角色特征 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | +| | 人物长相 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | +| | 其他能力 | - | - | - | - | - | - | - | - | | + +| kling-v2 | | 1:1 | 16:9 | 4:3 | 3:2 | 2:3 | 3:4 | 9:16 | 21:9 | +|---|---|---|---|---|---|---|---|---|---| +| 文生图 | - | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| | | | | | | | | | | +图生图 +多图参考生图 +✅ +✅ +✅ +✅ +✅ +✅ +✅ +✅ +| | 风格转绘 | ✅(生成图片分辨率与入参图相同,不支持单独设置分辨率) | | | | | | | | | +|---|---|---|---|---|---|---|---|---|---|---| +| | 其他能力 | - | - | - | - | - | - | - | - | | +| | kling-v2-new | | 1:1 | 16:9 | 4:3 | 3:2 | 2:3 | 3:4 | 9:16 | 21:9 | +| 文生图 | - | - | - | - | - | - | - | - | - | | +| 图生图 | 风格转绘 | ✅(生成图片分辨率与入参图相同,不支持单独设置分辨率) | | | | | | | | | +| | 其他能力 | - | - | - | - | - | - | - | - | | +| | kling-v2-1 | | 1:1 | 16:9 | 4:3 | 3:2 | 2:3 | 3:4 | 9:16 | 21:9 | +| 文生图 | - | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | +| 图生图 | 通用垫图 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | - | | +| | 角色特征 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | +| | 人物长相 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | +| | 多图参考生图 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | +| | 风格转绘 | ✅(生成图片分辨率与入参图相同,不支持单独设置分辨率) | | | | | | | | | + +| kling-v3 | | 自定义长宽比(1K/2K) | 智能长宽比 | +|---|---|---|---| +| 文生图 | 单图生成 | ✅ | - | +| | 其他 | - | - | +| 图生图 | 单图生成 | ✅ | - | +| | 主体控制 | | | +(仅多图主体) +✅ +- +| | 其他 | - | - | | | | | +|---|---|---|---|---|---|---|---| +| | 与模型版本无关的能力 | 是否支持 | 描述 | | | | | +| 扩图 | ✅ | 可基于已有图片扩展内容 | | | | | | +| 其他 | - | | | | | | | +| | 模型 | kling-v1 | | kling-v1-5 | | kling-2 | | +| 模式 | 文生图 | 图生图 | 文生图 | 图生图 | 文生图 | 图生图 | | +| 清晰度 | 1K | 1K | 1K | 1K | 1K/2K | 1K | | + +2-1【Omni-Image】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/omni-image | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | + +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| model_name | string | 可选 | kling-image-o1 | 模型名称 | +枚举值:kling-image-o1,kling-v3-omni +| prompt | string | 必须 | 无 | 文本提示词,可包含正向描述和负向描述 | +|---|---|---|---|---| +可将提示词模板化来满足不同的图像生成需求 +不能超过2500个字符 +Omni模型可通过Prompt与图片等内容实现多种能力 +通过<<<>>>的格式来指定某个图片,如:<<>> +能力范围详见使用手册:可灵Omni模型使用指南 +| image_list | array | 可选 | 空 | 参考图列表 | +|---|---|---|---|---| +用key:value承载,如下: + +```json +[ + { + "image_url": "https://example.com/image.jpg" + } +] +``` + +支持传入图片Base64编码或图片URL(确保可访问) +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px,图片宽高比要在1:2.5 ~ 2.5:1之间 +参考主体数量与参考图片数量有关,参考主体数量和参考图片数量之和不得超过10 +image_url参数值不得为空 +| element_list | array | 可选 | 空 | 主体参考列表 | +|---|---|---|---|---| +基于主体库中主体的ID配置,用key:value承载,如下: + +```json +[ + { + "element_id": "your_element_id" + } +] +``` + +参考主体数量与参考图片数量有关,参考主体数量和参考图片数量之和不得超过10 +不同模型版本支持范围不同,详见当前文档2-0能力地图 +| resolution | string | 可选 | 1k | 生成图片的清晰度 | +|---|---|---|---|---| +枚举值:1k, 2k, 4k +1k:1K标清 +2k:2K高清 +4k:4K高清 +不同模型版本支持范围不同,详见当前文档2-0能力地图 +| result_type | string | 可选 | single | 生成结果单图/组图切换开关 | +|---|---|---|---|---| +枚举值:single,series +不同模型版本支持范围不同,详见当前文档2-0能力地图 +| n | int | 可选 | 1 | 生成图片数量 | +|---|---|---|---|---| +取值范围:[1,9] +当result_type值为series时,当前参数无效 +| series_amount | int | 可选 | 4 | 生成组图的图片数量 | +|---|---|---|---|---| +取值范围:[2, 9] +当result_type值为single时,当前参数无效 +不同模型版本支持范围不同,详见当前文档2-0能力地图 +| aspect_ratio | string | 可选 | auto | 生成图片的画面纵横比(宽:高) | +|---|---|---|---|---| +枚举值:16:9, 9:16, 1:1, 4:3, 3:4, 3:2, 2:3, 21:9, auto +其中:auto为根据传入内容智能生成图片宽高比 +参考原图横纵比生成新图时,当前参数无效 +不同模型版本支持范围不同,详见当前文档2-0能力地图 +| watermark_info | array | 可选 | 空 | 是否同时生成含水印的结果 | +|---|---|---|---|---| +通过enabled参数定义,用key:value承载,如下:: + +```json +{ + "enabled": true +} +``` + +暂不支持自定义水印 +| callback_url | string | 可选 | 无 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + +调用示例 +引入主体生成图像 + + +2-2【Omni-Image】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/omni-image/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 必须 | 无 | 图片生成的任务ID | +请求路径参数,直接将值填写在请求路径中 +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +创建任务时填写的external_task_id,与task_id两种查询方式二选一 +请求体 +无 +响应体 + + +2-3【Omni-Image】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/omni-image | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + +2-4【图像生成】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/generations | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +请您注意,为了保持命名统一,原 model字段变更为 model_name字段,未来请您使用该字段来指定需要调用的模型版本。 + +同时,我们保持了行为上的向前兼容,如您继续使用原 model字段,不会对接口调用有任何影响、不会有任何异常,等价于 model_name为空时的默认行为(即调用V1模型) +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| model_name | string | 可选 | kling-v1 | 模型名称 | +枚举值:kling-v1, kling-v1-5, kling-v2, kling-v2-new, kling-v2-1, kling-v3 +| prompt | string | 必须 | 无 | 正向文本提示词 | +|---|---|---|---|---| +不能超过2500个字符 +| negative_prompt | string | 可选 | 空 | 负向文本提示词 | +|---|---|---|---|---| +不能超过2500个字符 +注:图生图(即image字段不为空时)场景下,不支持负向提示词 +| image | string | 可选 | 空 | 参考图片 | +|---|---|---|---|---| +支持传入图片Base64编码或图片URL(确保可访问) +请注意,若您使用base64的方式,请确保您传递的所有图像数据参数均采用Base64编码格式。在提交数据时,请不要在Base64编码字符串前添加任何前缀,例如data:image/png;base64,。正确的参数格式应该直接是Base64编码后的字符串。 +示例: +正确的Base64编码参数: + +错误的Base64编码参数(包含data:前缀): + +请仅提供Base64编码的字符串部分,以便系统能够正确处理和解析您的数据。 +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px,图片宽高比介于1:2.5 ~ 2.5:1之间 +image_reference参数不为空时,当前参数必填 +| image_reference | string | 可选 | 无 | 图片参考类型 | +|---|---|---|---|---| +枚举值:subject(角色特征参考), face(人物长相参考) +使用face(人物长相参考)时,上传图片需仅含1张人脸。 +使用kling-v1-5且image参数不为空时,当前参数必填 +| image_fidelity | float | 可选 | 0.5 | 生成过程中对用户上传图片的参考强度 | +|---|---|---|---|---| +取值范围:[0,1],数值越大参考强度越大 +仅 kling-v1, kling-v1-5 支持当前参数 +| human_fidelity | float | 可选 | 0.45 | 面部参考强度,即参考图中人物五官相似度 | +|---|---|---|---|---| +取值范围:[0,1],数值越大参考强度越大 +仅image_reference参数为subject时生效 +仅 kling-v1-5 支持当前参数 +| element_list | array | 可选 | 空 | 主体参考列表 | +|---|---|---|---|---| +基于主体库中主体的ID配置,用key:value承载,如下: + +```json +[ + { + "element_id": "your_element_id" + } +] +``` + +参考主体数量与参考图片数量有关,参考主体数量和参考图片数量之和不得超过10 +| resolution | string | 可选 | 1k | 生成图片的清晰度 | +|---|---|---|---|---| +枚举值:1k, 2k +1k:1K标清 +2k:2K高清 +不同模型版本支持范围不同,详见当前文档2-0能力地图 +| n | int | 可选 | 1 | 生成图片数量 | +|---|---|---|---|---| +取值范围:[1,9] +| aspect_ratio | string | 可选 | 16:9 | 生成图片的画面纵横比(宽:高) | +|---|---|---|---|---| +枚举值:16:9, 9:16, 1:1, 4:3, 3:4, 3:2, 2:3, 21:9 +不同模型版本支持范围不同,详见当前文档2-0能力地图 +| watermark_info | array | 可选 | 空 | 是否同时生成含水印的结果 | +|---|---|---|---|---| +通过enabled参数定义,具体array格式如下: + +```json +{ + "enabled": true +} +``` + +暂不支持自定义水印 +| callback_url | string | 可选 | 无 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + +调用示例 +引入主体生成图像 + + +2-5【图像生成】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/generations/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 必须 | 无 | 图片生成的任务ID | +请求路径参数,直接将值填写在请求路径中 +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +创建任务时填写的external_task_id,与task_id两种查询方式二选一 +请求体 +无 +响应体 + + +2-6【图像生成】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/generations | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +/v1/images/generations?pageNum=1&pageSize=30 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + +2-7【多图参考生图】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/multi-image2image | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| model_name | string | 可选 | kling-v2 | 模型名称 | +枚举值:kling-v2, kling-v2-1 +| prompt | string | 可选 | 空 | 正向文本提示词 | +|---|---|---|---|---| +不能超过2500个字符 +| subject_image_list | array | 必须 | 无 | 参考主体图片列表 | +|---|---|---|---|---| +最多支持4张图片,最少支持1张图片,用key:value承载,如下: + +API端无裁剪逻辑,请直接上传已选主体后的片 +支持传入图片Base64编码或图片URL(确保可访问) +请注意,若您使用base64的方式,请确保您传递的所有图像数据参数均采用Base64编码格式。在提交数据时,请不要在Base64编码字符串前添加任何前缀,例如data:image/png;base64,。正确的参数格式应该直接是Base64编码后的字符串。 +示例: +正确的Base64编码参数: + +错误的Base64编码参数(包含data:前缀): + +请仅提供Base64编码的字符串部分,以便系统能够正确处理和解析您的数据。 +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px,图片宽高比要在1:2.5 ~ 2.5:1之间 +| scene_image | string | 可选 | 空 | 场景参考图 | +|---|---|---|---|---| +支持传入图片Base64编码或图片URL(确保可访问) +请注意,若您使用base64的方式,请确保您传递的所有图像数据参数均采用Base64编码格式。在提交数据时,请不要在Base64编码字符串前添加任何前缀,例如data:image/png;base64,。正确的参数格式应该直接是Base64编码后的字符串。 +示例: +正确的Base64编码参数: + +错误的Base64编码参数(包含data:前缀): + +请仅提供Base64编码的字符串部分,以便系统能够正确处理和解析您的数据。 +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px,图片宽高比介于1:2.5 ~ 2.5:1之间 +| style_image | string | 可选 | 空 | 风格参考图 | +|---|---|---|---|---| +支持传入图片Base64编码或图片URL(确保可访问) +请注意,若您使用base64的方式,请确保您传递的所有图像数据参数均采用Base64编码格式。在提交数据时,请不要在Base64编码字符串前添加任何前缀,例如data:image/png;base64,。正确的参数格式应该直接是Base64编码后的字符串。 +示例: +正确的Base64编码参数: + +错误的Base64编码参数(包含data:前缀): + +请仅提供Base64编码的字符串部分,以便系统能够正确处理和解析您的数据。 +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px,图片宽高比介于1:2.5 ~ 2.5:1之间 +| n | int | 可选 | 1 | 生成图片数量 | +|---|---|---|---|---| +取值范围:[1,9] +| aspect_ratio | string | 可选 | 16:9 | 生成图片的画面纵横比(宽:高) | +|---|---|---|---|---| +枚举值:16:9, 9:16, 1:1, 4:3, 3:4, 3:2, 2:3, 21:9 +| watermark_info | array | 可选 | 空 | 是否同时生成含水印的结果 | +|---|---|---|---|---| +通过enabled参数定义,具体array格式如下: + +```json +{ + "enabled": true +} +``` + +暂不支持自定义水印 +| callback_url | string | 可选 | 空 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 空 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + + +2-8【多图参考生图】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/multi-image2image/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 必须 | 无 | 图片生成的任务ID | +请求路径参数,直接将值填写在请求路径中 +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +创建任务时填写的external_task_id,与task_id两种查询方式二选一 +请求体 +无 +响应体 + + +2-9【多图参考生图】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/multi-image2image | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +/v1/images/generations?pageNum=1&pageSize=30 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + +2-10【扩图】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/editing/expand | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| image | string | 必须 | 空 | 参考图片 | +支持传入图片Base64编码或图片URL(确保可访问) +请注意,若您使用base64的方式,请确保您传递的所有图像数据参数均采用Base64编码格式。在提交数据时,请不要在Base64编码字符串前添加任何前缀,例如data:image/png;base64,。正确的参数格式应该直接是Base64编码后的字符串。 +示例: +正确的Base64编码参数: + +错误的Base64编码参数(包含data:前缀): + +请仅提供Base64编码的字符串部分,以便系统能够正确处理和解析您的数据。 +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片分辨率不小于300*300px,图片宽高比要在1:2.5 ~ 2.5:1之间 +| up_expansion_ratio | float | 必须 | 0 | 向上扩充范围;基于原图高度的倍数而计算 | +|---|---|---|---|---| +取值范围:[0,2],新图片整体面积不得超过原图片3倍 +如原图高20,当前参数值为0.1,则: +原图顶边距离新图顶边为20 x 0.1 = 2,区域内均为扩图范围 +| down_expansion_ratio | float | 必须 | 0 | 向下扩充范围;基于原图高度的倍数而计算 | +|---|---|---|---|---| +取值范围:[0,2],新图片整体面积不得超过原图片3倍 +如原图高20,当前参数值为0.2,则: +原图底边距离新图底边为20 x 0.2 = 4,区域内均为扩图范围 +| left_expansion_ratio | float | 必须 | 0 | 向左扩充范围;基于原图宽度的倍数而计算 | +|---|---|---|---|---| +取值范围:[0,2],新图片整体面积不得超过原图片3倍 +如原图宽30,当前参数值为0.3,则: +原图左边距离新图左边为30 x 0.3 = 9,区域内均为扩图范围 +| right_expansion_ratio | float | 必须 | 0 | 向右扩充范围;基于原图宽度的倍数而计算 | +|---|---|---|---|---| +取值范围:[0,2],新图片整体面积不得超过原图片3倍 +如原图宽30,当前参数值为0.4,则: +原图右边距离新图右边为30 x 0.4 = 12,区域内均为扩图范围 +| prompt | string | 可选 | 无 | 正向文本提示词 | +|---|---|---|---|---| +不能超过2500个字符 +| n | int | 可选 | 1 | 生成图片数量 | +|---|---|---|---|---| +取值范围:[1,9] +| watermark_info | array | 可选 | 空 | 是否同时生成含水印的结果 | +|---|---|---|---|---| +通过enabled参数定义,具体array格式如下: + +```json +{ + "enabled": true +} +``` + +暂不支持自定义水印 +| callback_url | string | 可选 | 无 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + +示例代码 + + +2-11【扩图】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/editing/expand/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 必须 | 无 | 图片生成的任务ID | +请求路径参数,直接将值填写在请求路径中 +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +创建任务时填写的external_task_id,与task_id两种查询方式二选一 +请求体 +无 +响应体 + + +2-12【扩图】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/editing/expand | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + +2-13【通用】智能补全主体图 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/general/ai-multi-shot | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| element_frontal_image | string | 必须 | 无 | 主体正面参考图 | +支持传入图片Base64编码或图片URL(确保可访问) +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px,图片宽高比要在1:2.5 ~ 2.5:1之间 +| callback_url | string | 可选 | 空 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 空 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + + +2-14【通用】查询智能补充主体图任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/general/ai-multi-shot/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 必须 | 无 | 任务ID | +请求路径参数,直接将值填写在请求路径中 +请求体 +无 +响应体 + + +2-15【通用】查询智能补充主体图任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/general/ai-multi-shot | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | + +查询参数 +/v1/general/ai-multi-shot?pageNum=1&pageSize=30 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + + +三、视频生成 +3-0 能力地图 +| kling-video-o1 | | std(3s~10s) | pro(3s~10s) | +|---|---|---|---| +| 文生视频 | 单镜头视频生成 | ✅(仅5s、10s) | ✅(仅5s、10s) | +| | 声音控制(人声控制) | ❌ | ❌ | +| | 其他 | - | - | +| 图生视频 | 单镜头视频生成(仅首帧) | ✅(仅5s、10s) | ✅(仅5s、10s) | +| | 首尾帧(一镜到底) | ✅ | ✅ | +| | 主体控制 | | | +(仅多图主体) +✅ +✅ +| | 视频参考(含视频编辑) | ✅ | ✅ | | +|---|---|---|---|---| +| | 声音控制(人声控制) | ❌ | ❌ | | +| | 其他 | - | - | | +| | kling-v3-omni | | std(3s~15s) | pro(3s~15s) | +| 文生视频 | 单镜头视频生成 | ✅ | ✅ | | +| | 多镜头视频生成 | ✅ | ✅ | | +| | 声音控制(人声控制) | ❌ | ❌ | | +| | 其他 | - | - | | +| 图生视频 | 单镜头视频生成 | ✅ | ✅ | | +| | 多镜头视频生成 | ✅ | ✅ | | +| | 首尾帧(一镜到底) | ✅ | ✅ | | +| | 主体控制 | | | | +(视频角色主体+多图主体) +✅ +✅ +| | 视频参考 | ✅(仅3s~10s) | ✅(仅3s~10s) | +|---|---|---|---| +| | 声音控制(人声控制) | ❌ | ❌ | +| | 其他 | - | - | + +| kling-v1 | | std 5s | std 10s | pro 5s | pro10s | +|---|---|---|---|---|---| +| 文生视频 | 视频生成 | ✅ | ✅ | ✅ | ✅ | +| | 运镜控制 | ✅ | - | - | - | +| 图生视频 | 视频生成 | ✅ | ✅ | ✅ | ✅ | +| | 首尾帧 | ✅ | - | ✅ | - | +| | 运动笔刷 | ✅ | - | ✅ | - | +| | 其他能力 | - | - | - | - | +| 视频续写 | | | | | | +(不支持设置负向提示词和参考强度) + +✅ +✅ +✅ +✅ +| 视频特效-双人特效 | +|---| +拥抱,亲吻,比心 + +✅ +✅ +✅ +✅ +| 其他 | | - | - | - | - | | +|---|---|---|---|---|---|---| +| | kling-v1-5 | | std 5s | std 10s | pro 5s | pro10s | +| 文生视频 | 视频生成 | - | - | - | - | | +| | 其他能力 | - | - | - | - | | +| 图生视频 | 视频生成 | ✅ | ✅ | ✅ | ✅ | | +| | 首尾帧 | - | - | ✅ | ✅ | | +| | 仅尾帧 | - | - | ✅ | ✅ | | +| | 运动笔刷 | - | - | ✅ | - | | +| | 运镜控制 | | | | | | +(仅simple) +- +- +✅ +- +| | 其他能力 | - | - | - | - | +|---|---|---|---|---|---| +| 视频续写 | | ✅ | ✅ | ✅ | ✅ | +| 视频特效-双人特效 | | | | | | +拥抱,亲吻,比心 + +✅ +✅ +✅ +✅ +| 其他 | | - | - | - | - | +|---|---|---|---|---|---| + +| kling-v1-6 | | std 5s | std 10s | pro 5s | pro10s | +|---|---|---|---|---|---| +| 文生视频 | 视频生成 | ✅ | ✅ | ✅ | ✅ | +| | 其他能力 | - | - | - | - | +| 图生视频 | 视频生成 | ✅ | ✅ | ✅ | ✅ | +| | 首尾帧 | - | - | ✅ | ✅ | +| | 仅尾帧 | - | - | ✅ | ✅ | +| | 其他能力 | - | - | - | - | +| 多图参考生视频 | | ✅ | ✅ | ✅ | ✅ | +| 多模态视频编辑 | | ✅ | ✅ | ✅ | ✅ | +| 视频续写 | | ✅ | ✅ | ✅ | ✅ | +| 视频特效-双人特效 | | | | | | +拥抱,亲吻,比心 + +✅ +✅ +✅ +✅ +| | kling-v2-master | | 5s | 10s | | | +|---|---|---|---|---|---|---| +| 文生视频 | 视频生成 | ✅ | ✅ | | | | +| | 其他能力 | - | - | | | | +| 图生视频 | 视频生成 | ✅ | ✅ | | | | +| | 其他能力 | - | - | | | | +| 其他 | | - | - | | | | +| | kling-v2-1 | | std 5s | std 10s | pro 5s | pro10s | +| 文生视频 | 全部能力 | - | - | - | - | | +| 图生视频 | 视频生成 | ✅ | ✅ | ✅ | ✅ | | +| | 首尾帧 | - | - | ✅ | ✅ | | +| | 其他 | - | - | - | - | | +| 其他 | | - | - | - | - | | + +| kling-v2-1-master | | 5s | 10s | | | | | | +|---|---|---|---|---|---|---|---|---| +| 文生视频 | 视频生成 | ✅ | ✅ | | | | | | +| | 其他能力 | - | - | | | | | | +| 图生视频 | 视频生成 | ✅ | ✅ | | | | | | +| | 其他能力 | - | - | | | | | | +| 其他 | | - | - | | | | | | +| | kling-v2-5-turbo | | std 5s | std 10s | pro 5s | pro10s | | | +| 文生视频 | 视频生成 | ✅ | ✅ | ✅ | ✅ | | | | +| | 其他 | - | - | - | - | | | | +| 图生视频 | 视频生成 | ✅ | ✅ | ✅ | ✅ | | | | +| | 首尾帧 | - | - | ✅ | ✅ | | | | +| | 其他 | - | - | - | - | | | | +| 其他 | | - | - | - | - | | | | +| | kling-v2-6 | | std 5s | std 10s | std 其他时长 | pro 5s | pro10s | pro 其他时长 | +| 文生视频 | 视频生成 | ✅(仅无声视频) | ✅(仅无声视频) | - | ✅ | ✅ | - | | +| | 其他 | - | - | - | - | - | - | | +| 图生视频 | 视频生成 | ✅(仅无声视频) | ✅(仅无声视频) | - | ✅ | ✅ | - | | +| | 首尾帧 | - | - | - | ✅(仅无声视频) | ✅(仅无声视频) | - | | +| | 声音控制(人声控制) | - | - | - | ✅ | ✅ | - | | +| | 动作控制 | - | - | ✅ | - | - | ✅ | | +| | 其他 | - | - | - | - | - | - | | + +| kling-v3 | | std(3~15s) | pro(3~15s) | +|---|---|---|---| +| 文生视频 | 单镜头视频生成 | ✅ | ✅ | +| | 多镜头视频生成 | ✅ | ✅ | +| | 声音控制(人声控制) | ❌ | ❌ | +| | 其他 | - | - | +| 图生视频 | 单镜头视频生成(仅首帧) | ✅ | ✅ | +| | 多镜头视频生成 | ✅ | ✅ | +| | 首尾帧(一镜到底) | ✅ | ✅ | +| | 主体控制 | | | +(视频角色主体+多图主体) +✅ +✅ +| | 动作控制 | ✅ | ✅ | | | | | | | | +|---|---|---|---|---|---|---|---|---|---|---| +| | 声音控制(人声控制) | ❌ | ❌ | | | | | | | | +| | 其他 | - | - | | | | | | | | +| | 与模型版本无关的能力 | 是否支持 | 描述 | | | | | | | | +| 数字人 | ✅ | 只需一张照片即可生成数字人播报类视频 | | | | | | | | | +| 对口型 | ✅ | 可结合文案或音频,驱动视频中角色的口型 | | | | | | | | | +| 视频生音效 | ✅ | 支持为所有可灵模型生成的视频和用户上传的符合视频格式要求的视频添加音效 | | | | | | | | | +| 文生音效 | - | 支持通过输入文本描述(prompt)生成音效 | | | | | | | | | +| 其他 | - | - | | | | | | | | | +| | 模型 | kling-v1 | | kling-v1-5 图生视频 | | kling-v1-6 图生视频 | | kling-v1-6 文生视频 | | kling-v2 Master | +| 模式 | STD | PRO | STD | PRO | STD | PRO | STD | PRO | - | | +| 分辨率 | 720p | 720p | 720p | 1080p | 720p | 1080p | 720p | 1080p | 720p | | +| 帧率 | 30fps | 30fps | 30fps | 30fps | 30fps | 30fps | 24fps | 24fps | 24fps | | + +| 模型版本 | kling-v2-1 图生视频 | | kling-v2-1 Master | kling-v2-5 图生视频 | kling-v2-5 文生视频 | +|---|---|---|---|---|---| +| 模式 | STD | PRO | - | PRO | PRO | +| 分辨率 | 720p | 1080p | 1080p | 1080p | 1080p | +| 帧率 | 24fps | 24fps | 24fps | 24fps | 24fps | + +3-1【Omni-Video】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/omni-video | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| model_name | string | 可选 | kling-video-o1 | 模型名称 | +枚举值:kling-video-o1, kling-v3-omni +| multi_shot | boolean | 可选 | false | 是否生成多镜头视频 | +|---|---|---|---|---| +当前参数为true时,prompt参数无效,且不支持设定首尾帧生视频 +当前参数为false时,shot_type参数及multi_prompt参数无效 +| shot_type | string | 可选 | 空 | 分镜方式 | +|---|---|---|---|---| +枚举值:customize, intelligence +当multi_shot参数为true时,当前参数必填 +| prompt | string | 可选 | 空 | 文本提示词,可包含正向描述和负向描述 | +|---|---|---|---|---| +可将提示词模板化来满足不同的视频生成需求 +Omni模型可通过Prompt与主体、图片、视频等内容实现多种能力 +通过<<<>>>的格式来指定某个主体、图片、视频,如:<<>>、<<>>、<<>> +更多信息详见:可灵视频 3.0 Omni 使用指南 +长度不能超过2500个字符 +当“multi_shot参数为false”或“multi_shot参数为true且shot-type参数为intelligence”时,当前参数不得为空 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| multi_prompt | array | 可选 | 空 | 各分镜信息,如提示词、时长等 | +|---|---|---|---|---| +通过index、prompt、duration参数定义分镜序号及相应提示词和时长,其中: +最多支持6个分镜,最小支持1个分镜 +每个分镜相关内容的最大长度不超过512 +每个分镜的时长不大于当前任务的总时长,不小于1 +所有分镜的时长之和等于当前任务的总时长 +用key:value承载,如下: + +当mult_shot参数为true且shot_type参数为customize时,当前参数不得为空 +| image_list | array | 可选 | 空 | 参考图列表 | +|---|---|---|---|---| +包括主体、场景、风格等参考图片,也可作为首帧或尾帧生成视频;当作为首帧或尾帧生成视频时: +通过type参数来定义图片是否为首尾帧:first_frame为首帧,end_frame为尾帧;其中: +如图片非首帧或尾帧,请勿配置type参数 +暂时不支持仅尾帧,即有尾帧图时必须有首帧图 +首帧或首尾帧生视频时,不能使用视频编辑功能 +用key:value承载,如下: + +```json +[ + { + "image_url": "https://example.com/image.jpg" + } +] +``` + +支持传入图片Base64编码或图片URL(确保可访问) +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px,图片宽高比要在1:2.5 ~ 2.5:1之间 +参考图片数量与参考主体数量和参考主体类型有关,其中: +无参考视频+仅有多图主体时,参考图片与多图主体数量之和不得超过7; +无参考视频+有视频主体时,参考图片与多图主体数量之和不得超过4; +有参考视频+仅有多图主体时,参考图片与多图主体数量之和不得超过4; +使用kling-video-o1模型时,数组中超过2张图片时,不支持设置首尾帧 +image_url参数值不得为空 +| element_list | array | 可选 | 空 | 参考主体列表 | +|---|---|---|---|---| +基于主体库中主体的ID配置,用key:value承载,如下: + +主体分为视频定制主体(简称:视频角色主体)和图片定制主体(简称:多图主体),适用范围不同,请注意区分 +参考主体数量与主体类型、有无参考视频、参考图片数量等因素有关,其中: +当使用首帧或首尾帧生成视频时,kling-v3-omni模型最多支持3个主体; +当使用首尾帧生成视频时,kling-video-o1模型不支持主体; +无参考视频+仅有多图主体时,参考图片与多图主体数量之和不得超过7; +无参考视频+仅有视频角色主体时,视频角色主体数量不得超过3; +无参考视频+同时有视频角色主体和多图主体时,视频角色主体数量不得超过3,参考图片与多图主体数量之和不得超过4; +有参考视频+仅有多图主体时,参考图片与多图主体数量之和不得超过4; +有参考视频时,不支持使用视频角色主体; +更多主体信息详见:可灵「主体库 3.0」使用指南 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| video_list | array | 可选 | 空 | 参考视频,通过URL方式获取 | +|---|---|---|---|---| +可作为特征参考视频,也可作为待编辑视频,默认为待编辑视频;可选择性保留视频原声 +通过refer_type参数区分参考视频类型:feature为特征参考视频,base为待编辑视频 +参考视频为待编辑视频时,不能定义视频首尾帧 +通过keep_original_sound参数选择是否保留视频原声,yes为保留,no为不保留;当前参数对特征参考视频(feature)也生效 +有参考视频时,sound参数值只能为off +用key:value承载,如下: + +```json +[ + { + "video_url": "https://example.com/video.mp4", + "refer_type": "feature", + "keep_original_sound": "yes" + } +] +``` + +视频格式仅支持MP4/MOV +视频时长不少于3秒,上限与模型版本有关,详见能力地图 +视频宽高尺寸需介于720px(含)和2160px(含)之间 +视频帧率基于24fps~60fps,生成视频时会输出为24fps +至多仅支持上传1段视频,视频大小不超过200MB +video_url参数值不得为空 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| sound | string | 可选 | off | 生成视频时是否同时生成声音 | +|---|---|---|---|---| +枚举值:on,off +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| mode | string | 可选 | pro | 生成视频的模式 | +|---|---|---|---|---| +枚举值:std,pro +其中std:标准模式(标准),基础模式,性价比高 +其中pro:专家模式(高品质),高表现模式,生成视频质量更佳 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| aspect_ratio | string | 可选 | 空 | 生成视频的画面纵横比(宽:高) | +|---|---|---|---|---| +枚举值:16:9, 9:16, 1:1 +未使用首帧参考或视频编辑功能时,当前参数必填 +| duration | string | 可选 | 5 | 生成视频时长,单位s | +|---|---|---|---|---| +枚举值:3,4,5,6,7,8,9,10,11,12,13,14,15,其中: +使用视频编辑功能("refer_type":"base")时,输出结果与传入视频时长相同,此时当前参数无效;此时,按输入视频时长四舍五入取整计量计费 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| watermark_info | array | 可选 | 空 | 是否同时生成含水印的结果 | +|---|---|---|---|---| +通过enabled参数定义,具体array格式如下: + +```json +{ + "enabled": true +} +``` + +暂不支持自定义水印 +| callback_url | string | 可选 | 空 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 空 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + +场景调用示例 +图片/主体参考 +参考图片/主体里的角色/道具/场景等多种元素,灵活生成视频 + +| 技能及 Prompt 撰写格式 | 输入视频/图片/主体 | C端Prompt | C端生成效果 | B端请求体 | B端生成效果 | +|---|---|---|---|---|---| +| 参考图片 | | | | | | +参考 【@图片1】 的【参考内容,如人物】和 【@图片2】的【参考内容,如背景】生成视频,保持图片特征一致。 +** +【@图片1】的女孩和【@图片2】的男孩挽手并肩在东京街头散步 +* + +* +* + +| 参考主体 | +|---| +参考 【@主体1】 的【形象特征】与 【@主体2】的【场景特征】 生成视频,保持主体一致性。 +* +【@爆炸头的小男孩】走进【@温馨房间】 +* + +* +* + +| 参考图片和主体 | +|---| +参考 【@图片】 的【参考内容,如人物】和 【@主体】的【参考内容,如背景】生成视频。 +* +** + +* +* +* +* + +指令变换 +视频编辑,例如视频增加内容/删除内容/修改内容(主体/背景/局部/视频风格/物体颜色/天气/...)/切换景别/切换视角 + +| 技能及 Prompt 撰写格式 | 输入视频/图片/主体 | C端Prompt | C端生成效果 | B端请求体 | B端生成效果 | +|---|---|---|---|---|---| +| 在【@视频】中增加 [描述增加内容] | * | | | | | + + + +在【@视频】中的主体身后远处增加【@图片1】中的怪物,怪物从远处慢慢朝着主体走来 + +* + +* +* + +| 视频删除内容 | +|---| +删除【@视频】中的 [描述要删除内容] +* + +删除【@视频】中道路两侧的路人,保留马车 +* + +* +* + + +| 修改视频主体 | +|---| +把【@视频】中 [描述指定主体] 修改为【@图片】中 [目标主体]。 +* + +把【@视频】中的雕像改为【@图片】中的姜饼人 +* + +* + + +| 修改视频局部内容 | +|---| +把【@视频】中的【描述主体局部】修改为【描述目标内容】 + +* + +【@视频】中的长剑从剑鞘抽出时,只有露出的剑身逐渐变成【@图片1】中的8-bit像素风格的数字化剑身效果。剑鞘保持原样不变。随着剑刃滑出,像素块闪烁出现,剑身呈现复古像素光纹与数字方块跳动。 +* + +* +* + +| 修改视频背景 | +|---| +把【@视频】中的背景修改为【描述目标背景】 +* + + + +Convert the ocean in [@视频1] into the city in [@图片1] + + +* + +* +* +| 修改视频视角 | +|---| +把【@视频】修改为【目标视角】 + +* + +【@视频】生成这段视频的侧面极致特写,景深,晃动镜头 +* + +* +* + +| 视频绿幕抠像 | +|---| +把【@视频】的背景改成绿幕,保留 [描述保留内容] +* + +把【@视频】的背景改为绿幕,保留画面中的人物和水母 +* + +* +* + +| 改风格 | +|---| +把【@视频】转变为【指定风格】 +* + + +把【@视频】转变为美式卡通风格 +* + +* +* +| | | | | | | +|---|---|---|---|---|---| +| | | | | | | +| | | | | | | +| | | | | | | +| | | | | | | +| | | | | | | +| | | | | | | + +视频参考 +参考视频内容生成下一个镜头/上一个镜头,或者参考视频的风格/运镜方式进行视频生成 + +* +| 技能及 Prompt 撰写格式 | 输入视频/图片/主体 | C端Prompt | C端生成效果 | B端请求体 | B端生成效果 | +|---|---|---|---|---|---| +| 生成下一个镜头 | | | | | | +基于【@视频】生成下一个镜头:[描述镜头内容] +* + +基于【@视频】,生成下一个镜头:镜头位于后座,以中景拍摄前排中老年男子和年轻男性。两人身体微背向,形成对立三角结构。并向各自的车窗玻璃扭头向外看去。背景虚化。氛围紧张、压抑但克制,像密闭空间里的情绪对抗。柔和的自然光洒入车内,营造出暗淡的橄榄绿和棕色调,并带有细微的胶片颗粒感 + +* +* +* + +* + +| 生成上一个镜头 | +|---| +基于【@视频】生成上一个镜头:[描述镜头内容] + +* +基于【@视频】,生成前一个镜头:镜头向右移动跟拍身穿黑色西服的中老年男性,走向画面右侧的主驾驶门。然后中老年男性左手先拉开车门,然后坐进驾驶位,车轻微晃动。然后画面左侧前景的年轻男性一边开口说话一边看向中老年男性。 + +* +* +* + +* +| 参考视频运镜 | +|---| +将【@视频】的运镜方式运用到【@图片】上 + +* + +把【@图片1】作为首帧,并把【@视频】的运镜运用到【@图片1】上 +* + + + +* +* + +| 参考视频动作 | +|---| +让【@图片】使用【@视频】中 [在动作的角色] 相同的动作,运动起来 + +* + + +参考使用【@视频】中女孩的动作,让【@图片1】的女孩动起来 +* + +* + + + +首尾帧 +图生视频首尾帧 + +| 技能及 Prompt 撰写格式 | 输入视频/图片/主体 | C端Prompt | C端生成效果 | B端请求体 | B端生成效果 | +|---|---|---|---|---|---| +| 首帧生视频 | | | | | | +固定【@图片】作为首帧,【描述变化内容】 +* + +【@图片1】 固定为首帧,小男孩拿起牛奶用吸管喝了一口,露出微笑。 +* + +* +* + +| 首尾帧 | +|---| +固定【@图片1】作为首帧,【@图片2】作为尾帧,【描述过渡方式】。 +** +【@图片1】固定为首帧,【@图片2】 固定为尾帧, 【@图片1 】中人物往前跑动变成 【@图片2 】。 +* + + +* +* +多镜头和单镜头 +多镜头效果的图生视频 + +多镜头效果的文生视频 + +单镜头文生视频 + +| C端Prompt | C端生成效果 | B端请求体 | B端生成效果 | +|---|---|---|---| +| 美式卡通风格的动画视频。在一个阳光明媚的夏日午后,广阔的绿色山坡上野花盛开,天空湛蓝,飘浮着白云。两个8到10岁的小男孩,身穿休闲的T恤、短裤,头戴棒球帽,在山坡上追逐蝴蝶。镜头首先是一个广角全景展示他们在起伏的草地上奔跑,随后切换到低机位特写,捕捉他们挥舞捕虫网时坚定而夸张的面部表情。其中一个男孩跳起捕捉蝴蝶,另一个兴奋地指着远方。此时,画面背景的道路上出现了一辆汽车。随着镜头跟随汽车从远处驶近,男孩们停下了动作,拿着捕虫网,好奇地注视着这辆车。汽车最终停在男孩们身边,扬起一阵轻微的尘土,男孩们依然保持着好奇张望的姿势。光影鲜明多彩,充满了夏日冒险的快乐氛围。 | * | | | + +* +* + + + +FAQ +生成视频时长(duration)什么情况支持、什么情况不支持? +文生,图生(不含首尾帧):可选5s/10s +有视频输入(video_list不为空)且 使用视频编辑功能(类型=base)时:不可指定时长,跟视频对齐 +其他情况(不传视频+传图片+主体进行生视频,或者 传视频+视频类型=feature时),可选3-10s +怎么进行视频延长? +可以通过“视频参考”来实现,传入一段视频,通过prompt驱动模型“生成下一个镜头”或者“生成上一个镜头” + +生成视频宽高比(aspect_ratio)什么情况支持、什么情况不支持? +不支持:指令变换(视频编辑),图生视频(包括首尾帧) +支持:文生视频,图片/主体参考,视频参考-其他,视频参考-生成下一个/上一个镜头 + +3-2【Omni-Video】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/omni-video/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | + +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 可选 | 无 | 文生视频的任务ID | +请求路径参数,直接将值填写在请求路径中,与external_task_id两种查询方式二选一 +| external_task_id | string | 可选 | 无 | 文生视频的自定义任务ID | +|---|---|---|---|---| +创建任务时填写的external_task_id,与task_id两种查询方式二选一 +请求体 +无 +响应体 + + +3-3【Omni-Video】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/omni-video | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] + +请求体 +无 +响应体 + + +3-4【文生视频】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/text2video | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +请您注意,为了保持命名统一,原 model字段变更为 model_name字段,未来请您使用该字段来指定需要调用的模型版本。 +同时,我们保持了行为上的向前兼容,如您继续使用原 model字段,不会对接口调用有任何影响、不会有任何异常,等价于 model_name为空时的默认行为(即调用V1模型) +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| model_name | string | 可选 | kling-v1 | 模型名称 | +枚举值:kling-v1, kling-v1-6, kling-v2-master, kling-v2-1-master, kling-v2-5-turbo, kling-v2-6, kling-v3 +| multi_shot | boolean | 可选 | false | 是否生成多镜头视频 | +|---|---|---|---|---| +当前参数为true时,prompt参数无效 +当前参数为false时,shot_type参数及multi_prompt参数无效 +| shot_type | string | 可选 | 空 | 分镜方式 | +|---|---|---|---|---| +枚举值:customize,intelligence +当multi_shot参数为true时,当前参数必填 +| prompt | string | 可选 | 空 | 文本提示词,可包含正向描述和负向描述 | +|---|---|---|---|---| +可将提示词模板化来满足不同的视频生成需求 +Omni模型可通过Prompt与主体、图片、视频等内容实现多种能力 +通过<<<>>>的格式来指定某个主体、图片、视频,如:<<>>、<<>>、<<>> +更多信息详见:可灵视频 3.0 模型使用指南 +不能超过2500个字符 +用<<>>来指定音色,序号同voice_list参数所引用音色的排列顺序 +一次视频生成任务至多引用2个音色;指定音色时,sound参数值必须为on +语法结构越简单越好,如:男人<<>>说:“你好” +当voice_list参数不为空且prompt参数中引用音色ID时,视频生成任务按“有指定音色”计量计费 +当multi_shot参数为false或当shot_type参数为intelligence时,当前参数必填 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| multi_prompt | array | 可选 | 空 | 各分镜提示词,可包含正向描述和负向描述 | +|---|---|---|---|---| +通过index、prompt、duration参数定义分镜序号及相应提示词和时长,其中: +最多支持6个分镜,最小支持1个分镜 +每个分镜相关内容的最大长度不超过512 +每个分镜的时长不大于当前任务的总时长,不小于1 +所有分镜的时长之和等于当前任务的总时长 +用key:value承载,如下: + +当multi-shot参数为true且shot-type参数为customize时,当前参数不得为空 +| negative_prompt | string | 可选 | 空 | 负向文本提示词 | +|---|---|---|---|---| +不能超过2500个字符 +| voice_list | array | 可选 | 无 | 生成视频时所引用的音色的列表 | +|---|---|---|---|---| +一次视频生成任务至多引用2个音色 +当voice_list参数不为空且prompt参数中引用音色ID时,视频生成任务按“有指定音色”计量计费 +voice_id参数值通过音色定制接口返回,也可使用系统预置音色,详见音色定制相关API;非对口型API的voice_id +用key:value承载,如下: + +| sound | string | 可选 | off | 生成视频时是否同时生成声音 | +|---|---|---|---|---| +枚举值:on,off +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| cfg_scale | float | 可选 | 0.5 | 生成视频的自由度;值越大,模型自由度越小,与用户输入的提示词相关性越强 | +|---|---|---|---|---| +取值范围:[0, 1] +kling-v2.x模型不支持当前参数 +| mode | string | 可选 | std | 生成视频的模式 | +|---|---|---|---|---| +枚举值:std,pro +其中std:标准模式(标准),基础模式,性价比高 +其中pro:专家模式(高品质),高表现模式,生成视频质量更佳 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| camera_control | object | 可选 | 空 | 控制摄像机运动的协议(如未指定,模型将根据输入的文本/图片进行智能匹配) | +|---|---|---|---|---| +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| camera_control | +|---| +type +string +可选 +无 +预定义的运镜类型 +枚举值:"simple", "down_back", "forward_up", "right_turn_forward", "left_turn_forward" +simple:简单运镜,此类型下可在"config"中六选一进行运镜 +down_back:镜头下压并后退 ➡️ 下移拉远,此类型下config参数无需填写 +forward_up:镜头前进并上仰 ➡️ 推进上移,此类型下config参数无需填写 +right_turn_forward:先右旋转后前进 ➡️ 右旋推进,此类型下config参数无需填写 +left_turn_forward:先左旋并前进 ➡️ 左旋推进,此类型下config参数无需填写 +| camera_control | +|---| +config +object +可选 +无 +包含六个字段,用于指定摄像机在不同方向上的运动或变化 +当运镜类型指定simple时必填,指定其他类型时不填 +以下参数6选1,即只能有一个参数不为0,其余参数为0 +| config | +|---| +horizontal +float +可选 +无 +水平运镜,控制摄像机在水平方向上的移动量(沿x轴平移) +取值范围:[-10, 10],负值表示向左平移,正值表示向右平移 +| config | +|---| +vertical +float +可选 +无 +垂直运镜,控制摄像机在垂直方向上的移动量(沿y轴平移) +取值范围:[-10, 10],负值表示向下平移,正值表示向上平移 +| config | +|---| +pan +float +可选 +无 +水平摇镜,控制摄像机在水平面上的旋转量(绕y轴旋转) +取值范围:[-10, 10],负值表示绕y轴向左旋转,正值表示绕y轴向右旋转 +| config | +|---| +tilt +float +可选 +无 +垂直摇镜,控制摄像机在垂直面上的旋转量(沿x轴旋转) +取值范围:[-10, 10],负值表示绕x轴向下旋转,正值表示绕x轴向上旋转 +| config | +|---| +roll +float +可选 +无 +旋转运镜,控制摄像机的滚动量(绕z轴旋转) +取值范围:[-10, 10],负值表示绕z轴逆时针旋转,正值表示绕z轴顺时针旋转 +| config | +|---| +zoom +float +可选 +无 +变焦,控制摄像机的焦距变化,影响视野的远近 +取值范围:[-10, 10],负值表示焦距变长、视野范围变小,正值表示焦距变短、视野范围变大 +| aspect_ratio | string | 可选 | 16:9 | 生成视频的画面纵横比(宽:高) | +|---|---|---|---|---| +枚举值:16:9, 9:16, 1:1 +| duration | string | 可选 | 5 | 生成视频时长,单位s | +|---|---|---|---|---| +枚举值:3,4,5,6,7,8,9,10,11,12,13,14,15 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| watermark_info | array | 可选 | 空 | 是否同时生成含水印的结果 | +|---|---|---|---|---| +通过enabled参数定义,具体array格式如下: + +```json +{ + "enabled": true +} +``` + +暂不支持自定义水印 +| callback_url | string | 可选 | 无 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + +调用示例 +多镜头效果的文生视频 + + +3-5【文生视频】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/text2video/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 可选 | 无 | 文生视频的任务ID | +请求路径参数,直接将值填写在请求路径中,与external_task_id两种查询方式二选一 +| external_task_id | string | 可选 | 无 | 文生视频的自定义任务ID | +|---|---|---|---|---| +创建任务时填写的external_task_id,与task_id两种查询方式二选一 +请求体 +无 +响应体 + + +3-6【文生视频】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/text2video | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +/v1/videos/text2video?pageNum=1&pageSize=30 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + +3-7【图生视频】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/image2video | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +请您注意,为了保持命名统一,原 model字段变更为 model_name字段,未来请您使用该字段来指定需要调用的模型版本。 + +同时,我们保持了行为上的向前兼容,如您继续使用原 model字段,不会对接口调用有任何影响、不会有任何异常,等价于 model_name为空时的默认行为(即调用V1模型) +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| model_name | string | 可选 | kling-v1 | 模型名称 | +枚举值:kling-v1, kling-v1-5, kling-v1-6, kling-v2-master, kling-v2-1, kling-v2-1-master, kling-v2-5-turbo, kling-v2-6, kling-v3 +| image | string | 可选 | 空 | 参考图像 | +|---|---|---|---|---| +支持传入图片Base64编码或图片URL(确保可访问) +请注意,若您使用base64的方式,请确保您传递的所有图像数据参数均采用Base64编码格式。在提交数据时,请不要在Base64编码字符串前添加任何前缀,例如data:image/png;base64,。正确的参数格式应该直接是Base64编码后的字符串。 +示例: +正确的Base64编码参数: + +错误的Base64编码参数(包含data:前缀): + +请仅提供Base64编码的字符串部分,以便系统能够正确处理和解析您的数据。 +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px,图片宽高比介于1:2.5 ~ 2.5:1之间 +image参数与image_tail参数至少二选一,二者不能同时为空 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| image_tail | string | 可选 | 空 | 参考图像 - 尾帧控制 | +|---|---|---|---|---| +支持传入图片Base64编码或图片URL(确保可访问) +请注意,若您使用base64的方式,请确保您传递的所有图像数据参数均采用Base64编码格式。在提交数据时,请不要在Base64编码字符串前添加任何前缀,例如data:image/png;base64,。正确的参数格式应该直接是Base64编码后的字符串。 +示例: +正确的Base64编码参数: + +错误的Base64编码参数(包含data:前缀): + +请仅提供Base64编码的字符串部分,以便系统能够正确处理和解析您的数据。 +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px +image参数与image_tail参数至少二选一,二者不能同时为空 +image_tail参数、dynamic_masks/static_mask参数、camera_control参数三选一,不能同时使用 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| multi_shot | boolean | 可选 | false | 是否生成多镜头视频 | +|---|---|---|---|---| +当前参数为true时,prompt参数无效,且不支持设定首尾帧生视频 +当前参数为false时,shot_type参数及multi_prompt参数无效 +| shot_type | string | 可选 | 空 | 分镜方式 | +|---|---|---|---|---| +枚举值:customize,intelligence +当multi_shot参数为true时,当前参数必填 +| prompt | string | 可选 | 空 | 文本提示词,可包含正向描述和负向描述 | +|---|---|---|---|---| +可将提示词模板化来满足不同的视频生成需求 +Omni模型可通过Prompt与主体、图片、视频等内容实现多种能力 +通过<<<>>>的格式来指定某个主体、图片、视频,如:<<>>、<<>>、<<>> +更多信息详见:可灵视频 3.0 模型使用指南 +不能超过2500个字符 +用<<>>来指定音色,序号同voice_list参数所引用音色的排列顺序 +一次视频生成任务至多引用2个音色;指定音色时,sound参数值必须为on +语法结构越简单越好,如:男人<<>>说:“你好” +当voice_list参数不为空且prompt参数中引用音色ID时,视频生成任务按“有指定音色”计量计费 +当multi_shot参数为false或当shot_type参数为intelligence时,当前参数必填 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| multi_prompt | array | 可选 | 空 | 各分镜信息,如提示词、时长等 | +|---|---|---|---|---| +通过index、prompt、duration参数定义分镜序号及相应提示词和时长,其中: +最多支持6个分镜,最小支持1个分镜 +每个分镜相关内容的最大长度不超过512 +每个分镜的时长不大于当前任务的总时长,不小于1 +所有分镜的时长之和等于当前任务的总时长 +用key:value承载,如下: + +当mult_shot参数为true且shot_type参数为customize时,当前参数不得为空 +| negative_prompt | string | 可选 | 空 | 负向文本提示词 | +|---|---|---|---|---| +不能超过2500个字符 +| element_list | array | 可选 | 空 | 参考主体列表 | +|---|---|---|---|---| +基于主体库中主体的ID配置,用key:value承载,如下: + +最多支持3个参考主体 +主体分为视频定制主体(简称:视频角色主体)和图片定制主体(简称:多图主体),适用范围不同,请注意区分 +更多主体信息详见:可灵「主体库 3.0」使用指南 +element_list参数与voice_list参数互斥,不能共存 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| voice_list | array | 可选 | 无 | 生成视频时所引用的音色的列表 | +|---|---|---|---|---| +一次视频生成任务至多引用2个音色 +当voice_list参数不为空且prompt参数中引用音色ID时,视频生成任务按“有指定音色”计量计费 +voice_id参数值通过音色定制接口返回,也可使用系统预置音色,详见音色定制相关API;非对口型API的voice_id +element_list参数与voice_list参数互斥,不能共存 +用key:value承载,如下: + +```json +[ + { + "voice_id": "your_voice_id" + } +] +``` + +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| sound | string | 可选 | off | 生成视频时是否同时生成声音 | +|---|---|---|---|---| +枚举值:on,off +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| cfg_scale | float | 可选 | 0.5 | 生成视频的自由度;值越大,模型自由度越小,与用户输入的提示词相关性越强 | +|---|---|---|---|---| +取值范围:[0, 1] +kling-v2.x模型不支持当前参数 +| mode | string | 可选 | std | 生成视频的模式 | +|---|---|---|---|---| +枚举值:std,pro +其中std:标准模式(标准),基础模式,性价比高 +其中pro:专家模式(高品质),高表现模式,生成视频质量更佳 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| static_mask | string | 可选 | 无 | 静态笔刷涂抹区域(用户通过运动笔刷涂抹的 mask 图片) | +|---|---|---|---|---| +“运动笔刷”能力包含“动态笔刷 dynamic_masks”和“静态笔刷 static_mask”两种 +支持传入图片Base64编码或图片URL(确保可访问,格式要求同 image 字段) +图片格式支持.jpg / .jpeg / .png +图片长宽比必须与输入图片相同(即image字段),否则任务失败(failed) +static_mask 和 dynamic_masks.mask 这两张图片的分辨率必须一致,否则任务失败(failed) +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| dynamic_masks | array | 可选 | 无 | 动态笔刷配置列表 | +|---|---|---|---|---| +可配置多组(最多6组),每组包含“涂抹区域 mask”与“运动轨迹 trajectories”序列 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| dynamic_masks | +|---| +mask +string +可选 +无 +动态笔刷涂抹区域(用户通过运动笔刷涂抹的 mask 图片) +支持传入图片Base64编码或图片URL(确保可访问,格式要求同 image 字段) +图片格式支持.jpg / .jpeg / .png +图片长宽比必须与输入图片相同(即image字段),否则任务失败(failed) +static_mask 和 dynamic_masks.mask 这两张图片的分辨率必须一致,否则任务失败(failed) +| dynamic_masks | +|---| +trajectories +array +可选 +无 +运动轨迹坐标序列 +生成5s的视频,轨迹长度不超过77,即坐标个数取值范围:[2, 77] +轨迹坐标系,以图片左下角为坐标原点 +注1:坐标点个数越多轨迹刻画越准确,如只有2个轨迹点则为这两点连接的直线 +注2:轨迹方向以传入顺序为指向,以最先传入的坐标为轨迹起点,依次链接后续坐标形成运动轨迹 +| dynamic_masks | +|---| +trajectories +x +int +可选 +无 +轨迹点横坐标(在像素二维坐标系下,以输入图片image左下为原点的像素坐标) +| dynamic_masks | +|---| +trajectories +y +int +可选 +无 +轨迹点纵坐标(在像素二维坐标系下,以输入图片image左下为原点的像素坐标) +| camera_control | object | 可选 | 空 | 控制摄像机运动的协议(如未指定,模型将根据输入的文本/图片进行智能匹配) | +|---|---|---|---|---| +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| camera_control | +|---| +type +string +可选 +无 +预定义的运镜类型 +枚举值:"simple", "down_back", "forward_up", "right_turn_forward", "left_turn_forward" +simple:简单运镜,此类型下可在"config"中六选一进行运镜 +down_back:镜头下压并后退 ➡️ 下移拉远,此类型下config参数无需填写 +forward_up:镜头前进并上仰 ➡️ 推进上移,此类型下config参数无需填写 +right_turn_forward:先右旋转后前进 ➡️ 右旋推进,此类型下config参数无需填写 +left_turn_forward:先左旋并前进 ➡️ 左旋推进,此类型下config参数无需填写 +| camera_control | +|---| +config +object +可选 +无 +包含六个字段,用于指定摄像机在不同方向上的运动或变化 +当运镜类型指定simple时必填,指定其他类型时不填 +以下参数6选1,即只能有一个参数不为0,其余参数为0 +| config | +|---| +horizontal +float +可选 +无 +水平运镜,控制摄像机在水平方向上的移动量(沿x轴平移) +取值范围:[-10, 10],负值表示向左平移,正值表示向右平移 +| config | +|---| +vertical +float +可选 +无 +垂直运镜,控制摄像机在垂直方向上的移动量(沿y轴平移) +取值范围:[-10, 10],负值表示向下平移,正值表示向上平移 +| config | +|---| +pan +float +可选 +无 +水平摇镜,控制摄像机在水平面上的旋转量(绕y轴旋转) +取值范围:[-10, 10],负值表示绕y轴向左旋转,正值表示绕y轴向右旋转 +| config | +|---| +tilt +float +可选 +无 +垂直摇镜,控制摄像机在垂直面上的旋转量(沿x轴旋转) +取值范围:[-10, 10],负值表示绕x轴向下旋转,正值表示绕x轴向上旋转 +| config | +|---| +roll +float +可选 +无 +旋转运镜,控制摄像机的滚动量(绕z轴旋转) +取值范围:[-10, 10],负值表示绕z轴逆时针旋转,正值表示绕z轴顺时针旋转 +| config | +|---| +zoom +float +可选 +无 +变焦,控制摄像机的焦距变化,影响视野的远近 +取值范围:[-10, 10],负值表示焦距变长、视野范围变小,正值表示焦距变短、视野范围变大 +| duration | string | 可选 | 5 | 生成视频时长,单位s | +|---|---|---|---|---| +枚举值:3,4,5,6,7,8,9,10,11,12,13,14,15 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| watermark_info | array | 可选 | 空 | 是否同时生成含水印的结果 | +|---|---|---|---|---| +通过enabled参数定义,具体array格式如下: + +```json +{ + "enabled": true +} +``` + +暂不支持自定义水印 +| callback_url | string | 可选 | 无 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + +场景调用示例 +多镜头效果的图生视频 + +引用主体及主体音色的图生视频 + +指定音色生成视频 + +音色定制 + +3-8【图生视频】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/image2video/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 可选 | 无 | 图生视频的任务ID | +请求路径参数,直接将值填写在请求路径中,与external_task_id两种查询方式二选一 +| external_task_id | string | 可选 | 无 | 图生视频的自定义任务ID | +|---|---|---|---|---| +创建任务时填写的external_task_id,与task_id两种查询方式二选一 +请求体 +无 +响应体 + + + +3-9【图生视频】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/image2video | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +/v1/videos/image2video?pageNum=1&pageSize=30 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + + +3-10【多图参考生视频】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/multi-image2video | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| model_name | string | 可选 | kling-v1-6 | 模型名称 | +枚举值:kling-v1-6 +| image_list | array | 必须 | 空 | 参考图像列表 | +|---|---|---|---|---| +最多支持4张图片,用key:value承载,如下: + +API端无裁剪逻辑,请直接上传已选主体后的图片 +支持传入图片Base64编码或图片URL(确保可访问) +请注意,若您使用base64的方式,请确保您传递的所有图像数据参数均采用Base64编码格式。在提交数据时,请不要在Base64编码字符串前添加任何前缀,例如data:image/png;base64,。正确的参数格式应该直接是Base64编码后的字符串。 +示例: +正确的Base64编码参数: + +错误的Base64编码参数(包含data:前缀): + +请仅提供Base64编码的字符串部分,以便系统能够正确处理和解析您的数据。 +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于200px,图片宽高比要在1:2.5 ~ 2.5:1之间 +| prompt | string | 必须 | 无 | 正向文本提示词 | +|---|---|---|---|---| +不能超过2500个字符 +| negative_prompt | string | 可选 | 空 | 负向文本提示词 | +|---|---|---|---|---| +不能超过2500个字符 +| mode | string | 可选 | std | 生成视频的模式 | +|---|---|---|---|---| +枚举值:std,pro +其中std:标准模式(标准),基础模式,性价比高 +其中pro:专家模式(高品质),高表现模式,生成视频质量更佳 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| duration | string | 可选 | 5 | 生成视频时长,单位s | +|---|---|---|---|---| +枚举值:5,10 +| aspect_ratio | string | 可选 | 16:9 | 生成图片的画面纵横比(宽:高) | +|---|---|---|---|---| +枚举值:16:9, 9:16, 1:1 +| watermark_info | array | 可选 | 空 | 是否同时生成含水印的结果 | +|---|---|---|---|---| +通过enabled参数定义,具体array格式如下: + +```json +{ + "enabled": true +} +``` + +暂不支持自定义水印 +| callback_url | string | 可选 | 无 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + + +3-11【多图参考生视频】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/multi-image2video/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 可选 | 无 | 多图参考生视频的任务ID | +请求路径参数,直接将值填写在请求路径中,与external_task_id两种查询方式二选一 +| external_task_id | string | 可选 | 无 | 多图参考生视频的自定义任务ID | +|---|---|---|---|---| +创建任务时填写的external_task_id,与task_id两种查询方式二选一 +请求体 +无 +响应体 + + +3-12【多图参考生视频】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/multi-image2video | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +/v1/videos/multi-image2video?pageNum=1&pageSize=30 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + +3-13【动作控制】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/motion-control | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 + +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| model_name | string | 可选 | kling-v2-6 | 模型名称 | +枚举值:kling-v2-6, kling-v3 +| prompt | string | 可选 | 空 | 文本提示词,可包含正向描述和负向描述 | +|---|---|---|---|---| +可通过提示词为画面增加元素、实现运镜效果等,详见可灵「动作控制」使用指南 +不能超过2500个字符 +| image_url | string | 必须 | 无 | 参考图像,生成视频中的人物、背景等元素均已参考图为准 | +|---|---|---|---|---| +视频内容需满足以下要求: +人物比例尽量与参考动作比例一致,尽量避免全身动作驱动半身人物进行生成 +人物需要漏出清晰的上半身或全身的肢体及头部,避免遮挡 +画面中人物避免存在极端朝向,比如倒立、平卧等。人物占画面比例不得太低 +支持真实/风格化的角色(包括人物/类人动物/部分纯动物/部分类人肢体比例的角色)通过 +包含支持传入图片Base64编码或图片URL(确保可访问) +请注意,若您使用base64的方式,请确保您传递的所有图像数据参数均采用Base64编码格式。在提交数据时,请不要在Base64编码字符串前添加任何前缀,例如data:image/png;base64,。正确的参数格式应该直接是Base64编码后的字符串。 +示例: +正确的Base64编码参数: + +错误的Base64编码参数(包含data:前缀): + +请仅提供Base64编码的字符串部分,以便系统能够正确处理和解析您的数据。 +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸介于300px~65536px,图片宽高比介于1:2.5 ~ 2.5:1之间 +| video_url | string | 必须 | 无 | 参考视频的获取链接。生成视频中的人物动作与参考视频一致。 | +|---|---|---|---|---| +视频内容需满足以下要求: +人物需要漏出清晰的上半身或全身的全部肢体及头部,避免遮挡 +建议上传1人动作视频,2人及以上会取画面占比最大的人物动作进行生成 +推荐使用真人动作,部分风格化的人物/类人肢体比例可以通过 +动作视频一镜到底,角色始终出现在画面中,避免切镜、运镜等。否则会被截取 +动作避免过快,相对平稳的动作生成效果更佳 +视频文件支持.mp4/.mov,文件大小不超过100MB,仅支持长宽的边长均位于340px~3850px之间,上述校验不通过会返回错误码等信息 +视频时长下限不短于3秒,时长上限与人物朝向参考(character_orientation)有关: +当人物朝向与视频中人物一致时,视频时长最长可达30秒; +当人物朝向与图片中人物一致时,视频时长最长可达10秒; +如果您的动作难度比较高、速度比较快,有一定概率生成不足上传视频时长的结果,因为模型只能提取有效动作时长进行生成,最短提取出3s可用连续动作即可生成。请注意,因此消耗的积分将无法退还,建议适当调整动作难度与速度 +积分扣减计算以输出视频时长为准 +系统会校验视频内容,如有问题会返回错误码等信息 +| element_list | array | 可选 | 空 | 主体参考列表 | +|---|---|---|---|---| +基于主体库中主体的ID配置,用key:value承载,如下: + +引用主体时,生成的视频暂时只能参考视频中的人物朝向 +暂时仅支持引入1个主体 +| keep_original_sound | string | 可选 | yes | 可选择是否保留视频原声 | +|---|---|---|---|---| +枚举值:yes,no +其中yes:保留视频原声 +其中no:不保留视频原声 +| character_orientation | string | 必须 | 无 | 生成视频中人物的朝向,可选择与图片一致或与视频一致 | +|---|---|---|---|---| +枚举值:image,video,其中: +其中image:与图片中人物朝向一致;此时参考视频时长不得超过10秒; +其中video:与视频中人物朝向一致;此时参考视频时长不得超过30秒; +引用主体时,生成的视频暂时只能参考视频中的人物朝向 +| mode | string | 必选 | 无 | 生成视频的模式 | +|---|---|---|---|---| +枚举值:std,pro +其中std:标准模式(标准),基础模式,性价比高 +其中pro:专家模式(高品质),高表现模式,生成视频质量更佳 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| watermark_info | array | 可选 | 空 | 是否同时生成含水印的结果 | +|---|---|---|---|---| +通过enabled参数定义,具体array格式如下: + +```json +{ + "enabled": true +} +``` + +暂不支持自定义水印 +| callback_url | string | 可选 | 空 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 空 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + +场景调用示例 + + +3-14【动作控制】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/motion-control/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 必须 | 无 | 视频生成的任务ID | +请求路径参数,直接将值填写在请求路径中 +| external_task_id | string | 可选 | 空 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 +请求体 +无 +响应体 + +3-15【动作控制】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/motion-control | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +/v1/videos/motion-control?pageNum=1&pageSize=30 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + +3-16【多模态视频编辑】初始化待编辑视频 +操作指引:使用“多模态视频编辑”功能时,需先对原始视频进行初始化处理。其中,在替换或删除现有视频中的元素时,需先标记视频中相关元素。 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/multi-elements/init-selection | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | + +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| video_id | string | 可选 | 空 | 视频ID,从历史作品中选择待编辑的视频,仅支持仅30天时间生成的视频作品 | +仅支持时长≥2秒且≤5秒,或≥7秒且≤10秒的视频 +与video_url参数相关,不能同时为空,也不能同时有值 +| video_url | string | 可选 | 空 | 获取视频的URL,上传时传视频下载链接,编辑选区时传接口返回的视频URL | +|---|---|---|---|---| +仅支持MP4和MOV格式 +仅支持时长≥2秒且≤5秒,或≥7秒且≤10秒的视频 +视频宽高尺寸需介于720px(含)和2160px(含)之间 +仅支持上传24、30或60fps的视频 +与video_url参数相关,不能同时为空,也不能同时有值 +响应体 + + +3-17【多模态视频编辑】增加视频选区 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/multi-elements/add-selection | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | + +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| session_id | string | 必须 | 无 | 会话ID,会基于视频初始化任务生成,不会随编辑选区行为而改变 | +| frame_index | | | | | + +int +必须 +无 +帧号 +最多支持添加10个标记帧,即最多基于10帧标记视频选区 +1次仅支持标记1帧 +| points | object[] | 必须 | 无 | 点选坐标,用x、y表示 | +|---|---|---|---|---| +取值范围:[0,1],用百分比表示;[0,1]代表画面左上角 +支持同时增加多个标记点,某一帧最多可标记10个点 +响应体 + +示例代码 +解析图像分割结果 + +绘制图像分割图层 + + +3-18【多模态视频编辑】删减视频选区 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/multi-elements/delete-selection | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | + +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| session_id | string | 必须 | 无 | 会话ID,会基于视频初始化任务生成,不会随编辑选区行为而改变 | +| frame_index | int | 必须 | 无 | 帧号 | +| points | object[] | 必须 | 无 | 点选坐标,用x、y表示 | +取值范围:[0,1],用百分比表示;[0,1]代表画面左上角 +支持同时增加多个标记点 +坐标点需与增加视频选区时完全一致 +响应体 + + +3-19【多模态视频编辑】清除视频选区 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/multi-elements/clear-selection | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| session_id | string | 必须 | 无 | 会话ID,会基于视频初始化任务生成,不会随编辑选区行为而改变 | + +响应体 + + +3-20【多模态视频编辑】预览已选区视频 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/multi-elements/preview-selection | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| session_id | string | 必须 | 无 | 会话ID,会基于视频初始化任务生成,不会随编辑选区行为而改变 | +响应体 + + +3-21【多模态视频编辑】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/multi-elements/ | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | + +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| model_name | string | 可选 | kling-v1-6 | 模型名称 | +枚举值:kling-v1-6 +| session_id | string | 必须 | 无 | 会话ID | +|---|---|---|---|---| +会基于视频初始化任务生成,不会随编辑选区行为而改变 +| edit_mode | string | 必须 | 无 | 操作类型 | +|---|---|---|---|---| +枚举值:addition, swap, removal, 其中: +addition:增加元素 +swap:替换元素 +removal:删除元素 +| image_list | array | 可选 | 空 | 裁剪后的参考图像 | +|---|---|---|---|---| +增加视频元素时:当前参数必填,可上传1~2张图片 +编辑视频元素时:当前参数必填,仅可上传1张图片 +删除视频元素时,当前参数无需填写 +用key:value承载,如下: + +API端无裁剪逻辑,请直接上传已选主体后的图片 +支持传入图片Base64编码或图片URL(确保可访问) +请注意,若您使用base64的方式,请确保您传递的所有图像数据参数均采用Base64编码格式。在提交数据时,请不要在Base64编码字符串前添加任何前缀,例如data:image/png;base64,。正确的参数格式应该直接是Base64编码后的字符串。 +示例: +正确的Base64编码参数: + +错误的Base64编码参数(包含data:前缀): + +请仅提供Base64编码的字符串部分,以便系统能够正确处理和解析您的数据。 +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px +| prompt | string | 必须 | 无 | 正向文本提示词 | +|---|---|---|---|---| +用<<>>的格式来特指某个视频或某张图片,如<<>>、<<>> +为保证效果,提示词中需包含视频编辑所需的视频和图片(如有),如下文“推荐的Prompt模板” +不能超过2500个字符 +推荐的Prompt模板 +增加元素 +中文:基于<<>>中的原始内容,以自然生动的方式,将<<>>中的【】,融入<<>>的【】 +英文:Using the context of <<>>, seamlessly add [x] from <<>> +替换元素 +中文:使用<<>>中的 【】,替换<<>>中的 【】 +英文:swap [x] from <<>> for [x] from <<>> +删除元素 +中文:删除<<>>中的【】 +英文:Delete [x] from <<>> +注:中文的【】,英文的[x],是需要用户填写的部分 + +| negative_prompt | string | 可选 | 空 | 负向文本提示词 | +|---|---|---|---|---| +不能超过2500个字符 +| mode | string | 可选 | std | 生成视频的模式 | +|---|---|---|---|---| +枚举值:std,pro +其中std:标准模式(标准),基础模式,性价比高 +其中pro:专家模式(高品质),高表现模式,生成视频质量更佳 +| duration | string | 可选 | 5 | 生成视频时长,单位s | +|---|---|---|---|---| +枚举值:5,10 +支持且仅支持生成5s和10s的视频,对于生成不同时长的视频,对输入视频有时长会有所限制: +如生成5s时长视频,输入视频时长需≥2s且≤5s +如生成10s时长视频,输入视频时长需≥7s且≤10s +| watermark_info | array | 可选 | 空 | 是否同时生成含水印的结果 | +|---|---|---|---|---| +通过enabled参数定义,具体array格式如下: + +```json +{ + "enabled": true +} +``` + +暂不支持自定义水印 +| callback_url | string | 可选 | 空 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 空 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + + +3-22【多模态视频编辑】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/multi-elements/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 可选 | 无 | 多图参考生视频的任务ID | +请求路径参数,直接将值填写在请求路径中,与external_task_id两种查询方式二选一 +| external_task_id | string | 可选 | 无 | 多图参考生视频的自定义任务ID | +|---|---|---|---|---| +创建任务时填写的external_task_id,与task_id两种查询方式二选一 +请求体 +无 +响应体 + + +3-23【多模态视频编辑】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/multi-elements/ | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +/v1/videos/multi-image2video?pageNum=1&pageSize=30 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + +3-24【视频延长】创建任务 +注-1:视频延长是指对文生/图生视频结果进行时间上的延长,单次可延长4~5s,使用的模型和模式不可选择、与源视频相同 +注-2:被延长后的视频可以再次延长,但总视频时长不能超过3min +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/video-extend | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | + +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| video_id | string | 必须 | 无 | 视频ID | +支持通过文本、图片和视频延长生成的视频的ID(原视频不能超过3分钟) +请注意,基于目前的清理策略、视频生成30天之后会被清理,则无法进行延长 +| prompt | string | 可选 | 无 | 正向文本提示词 | +|---|---|---|---|---| +不能超过2500个字符 +| negative_prompt | string | 可选 | 无 | 负向文本提示词 | +|---|---|---|---|---| +不能超过2500个字符 +| cfg_scale | float | 可选 | 0.5 | 提示词参考强度 | +|---|---|---|---|---| +取值范围:[0,1],数值越大参考强度越大 +| watermark_info | array | 可选 | 空 | 是否同时生成含水印的结果 | +|---|---|---|---|---| +通过enabled参数定义,具体array格式如下: + +```json +{ + "enabled": true +} +``` + +暂不支持自定义水印 +| callback_url | string | 可选 | 无 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + + +3-25【视频延长】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/video-extend/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 必须 | 无 | 视频续写的任务ID | +请求路径参数,直接将值填写在请求路径中 +| external_task_id | string | 可选 | 空 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 +请求体 +无 +响应体 + + +3-26【视频延长】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/video-extend | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + +3-27【数字人】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/avatar/image2video | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| image | string | 必须 | 无 | 数字人参考图 | +支持传入图片Base64编码或图片URL(确保可访问) +请注意,若您使用base64的方式,请确保您传递的所有图像数据参数均采用Base64编码格式。在提交数据时,请不要在Base64编码字符串前添加任何前缀,例如data:image/png;base64,。正确的参数格式应该直接是Base64编码后的字符串。 +示例: +正确的Base64编码参数: + +错误的Base64编码参数(包含data:前缀): + +请仅提供Base64编码的字符串部分,以便系统能够正确处理和解析您的数据。 +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px,图片宽高比介于1:2.5 ~ 2.5:1之间 +| audio_id | string | 可选 | 空 | 通过试听接口生成的音频的ID | +|---|---|---|---|---| +仅支持使用30天内生成的、时长不短于2秒且不超过300秒的音频 +audio_id、sound_file参数二选一,不能同时为空,也不能同时有值 +| sound_file | string | 可选 | 空 | 音频文件 | +|---|---|---|---|---| +支持传入音频Base64编码或图音频URL(确保可访问) +音频文件支持.mp3/.wav/.m4a/.aac,文件大小不超过5MB,格式不匹配或文件过大会返回错误码等信息 +仅支持使用时长不短于2秒且不长于300秒的音频 +audio_id、sound_file参数二选一,不能同时为空,也不能同时有值 +系统会校验音频内容,如有问题会返回错误码等信息 +| prompt | string | 可选 | 空 | 正向文本提示词 | +|---|---|---|---|---| +可定义数字人动作、情绪及运镜等 +不能超过2500个字符 +| mode | string | 可选 | std | 生成视频的模式 | +|---|---|---|---|---| +枚举值:std,pro +其中std:标准模式(标准),基础模式,性价比高 +其中pro:专家模式(高品质),高表现模式,生成视频质量更佳 +不同模型版本、视频模式支持范围不同,详见当前文档3-0能力地图 +| watermark_info | array | 可选 | 空 | 是否同时生成含水印的结果 | +|---|---|---|---|---| +通过enabled参数定义,具体array格式如下: + +```json +{ + "enabled": true +} +``` + +暂不支持自定义水印 +| callback_url | string | 可选 | 无 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 空 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + + +3-28【数字人】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/avatar/image2video/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 必须 | 无 | 数字人的任务ID | +请求路径参数,直接将值填写在请求路径中 +| external_task_id | string | 可选 | 空 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 +请求体 +无 +响应体 + + +3-29【数字人】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/avatar/image2video | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + +3-30【对口型】人脸识别 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/identify-face | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| video_id | string | 可选 | 空 | 通过可灵AI生成的视频的ID | +用于指定视频、判断视频是否可用于对口型服务 +与video_url参数二选一填写,不能同时为空,也不能同时有值 +仅支持使用30天内生成的时长不超过60秒的视频 +| video_url | string | 可选 | 空 | 所上传视频的获取URL | +|---|---|---|---|---| +用于指定视频,并判断视频是否可用于对口型服务 +与video_id参数二选一填写,不能同时为空,也不能同时有值 +视频文件支持.mp4/.mov,文件大小不超过100MB,视频时长不超过60s且不短于2s,仅支持720p和1080p、长宽的边长均位于512px~2160px之间,上述校验不通过会返回错误码等信息 +系统会校验视频内容,如有问题会返回错误码等信息 + +响应体 + + +3-31【对口型】创建任务 +对口型创建任务接口已升级至全新版本,如需浏览旧版请移步可灵AI【对口型】(旧版)API文档 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/advanced-lip-sync | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| session_id | string | 必须 | 无 | 会话ID,会基于对口型人脸识别接口生成 | +| face_choose | | | | | + +string[] +必填 +无 +指定人脸对口型 +包括人脸ID、口型参考等内容等 +暂时仅支持指定单人对口型 +| face_choose | +|---| +face_id +string +必填 +无 +人脸ID +由人脸识别接口返回 +| face_choose | +|---| +audio_id +string +可选 +空 +通过试听接口生成的音频的ID +仅支持使用30天内生成的、时长不短于2秒且不超过60秒的音频 +audio_id、sound_file参数二选一,不能同时为空,也不能同时有值 +| face_choose | +|---| +sound_file +string +可选 +空 +音频文件 +支持传入音频Base64编码或图音频URL(确保可访问) +音频文件支持.mp3/.wav/.m4a/.aac,文件大小不超过5MB,格式不匹配或文件过大会返回错误码等信息 +仅支持使用时长不短于2秒且不长于60秒的音频 +audio_id、sound_file参数二选一,不能同时为空,也不能同时有值 +系统会校验音频内容,如有问题会返回错误码等信息 +| face_choose | +|---| +sound_start_time +long +必须 +无 +音频裁剪起点时间 +以原始音频开始时间为准,开始时间为0分0秒,单位ms +起点之前的音频会被裁剪,裁剪后音频不得短于2秒 +| face_choose | +|---| +sound_end_time +long +必须 +无 +音频裁剪终点时间 +以原始音频开始时间为准,开始时间为0分0秒,单位ms +终点之后的音频会被裁剪,裁剪后音频不得短于2秒 +终点时间不得晚于原始音频总时长 +| face_choose | +|---| +sound_insert_time +long +必须 +无 +裁剪后音频插入时间 +以视频开始时间为准,视频开始时间为0分0秒,单位ms +插入音频的时间范围与该人脸可对口型时间区间至少重合2秒时长 +插入音频的开始时间不得早于视频开始时间,插入音频的结束时间不得晚于视频结束时间 +| face_choose | +|---| +sound_volume +float +可选 +1 +音频音量大小;值越大,音量越大 +取值范围:[0, 2] +| face_choose | +|---| +original_audio_volume +float +可选 +1 +原始视频音量大小;值越大,音量越大 +取值范围:[0, 2] +原视频无声时,当前参数无效果 +| watermark_info | array | 可选 | 空 | 是否同时生成含水印的结果 | +|---|---|---|---|---| +通过enabled参数定义,具体array格式如下: + +```json +{ + "enabled": true +} +``` + +暂不支持自定义水印 +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 +| callback_url | string | 可选 | 无 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” + +响应体 + + +3-32【对口型】查询任务(单个) +对口型创建任务接口已升级至全新版本,如需浏览旧版请移步可灵AI【对口型】(旧版)API文档 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/advanced-lip-sync/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 必须 | 无 | 对口型的任务ID | +请求路径参数,直接将值填写在请求路径中 +请求体 +无 +响应体 + + +3-33【对口型】查询任务(列表) +对口型创建任务接口已升级至全新版本,如需浏览旧版请移步可灵AI【对口型】(旧版)API文档 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/advanced-lip-sync | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + +3-34【视频特效】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/effects | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +通用请求体 +当前一共支持 230 款特效,您可以根据调用 effect_scene 实现不同的效果,详细内容请见:特效模版中心 + +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| effect_scene | string | 必须 | 无 | 场景名称 | +枚举值:flash_drive, shush_my_dreams, french_elegance, finger_swipe, advent_of_flora, smooth_transition, raid_check, fortune_in_motion, chinese_trend, sedan_chair_dance, yangge_dance, good_luck_dance, laicai_dance, snow_night_kiss, eternal_kiss, color_mixing, palm_sized_figure, lantern_festival_cuju, unique_firework, unique_spring_couplets, horse_mask, fortune_knocks_cartoon, tangyuan_to_animal, hot_feet_dance, swag_dance, pigeon_dance, bloodline_dance, chanel_dance, cute_dance, love_theme_song, pumpitup_dance, city_to_village, fortune_god_transform, new_year_feast, ring_in_new, horse_year_firework, pet_vlogger, crystal_horse, lateral_shift_transition, drunk_dance, drunk_dance_pet, daoma_dance, bouncy_dance, smooth_sailing_dance, new_year_greeting, lion_dance, prosperity, great_success, golden_horse_fortune, red_packet_box, lucky_horse_year, lucky_red_packet, lucky_money_come, lion_dance_pet, dumpling_making_pet, fish_making_pet, pet_red_packet, lantern_glow, expression_challenge, overdrive, heart_gesture_dance, poping, martial_arts, running, nezha, motorcycle_dance, subject_3_dance, ghost_step_dance, phantom_jewel, zoom_out, cheers_2026, kiss_pro, fight_pro, hug_pro,heart_gesture_pro, dollar_rain_pro, pet_bee_pro, countdown_teleport, santa_random_surprise, magic_match_tree, bullet_time_360, happy_birthday, birthday_star, thumbs_up_pro, tiger_hug_pro, pet_lion_pro, surprise_bouquet, bouquet_drop, 3d_cartoon_1_pro, firework_2026, glamour_photo_shoot, box_of_joy, first_toast_of_the_year, my_santa_pic, santa_gift, steampunk_christmas, snowglobe, christmas_photo_shoot, ornament_crash​, santa_express, instant_christmas, particle_santa_surround, coronation_of_frost, building_sweater, spark_in_the_snow, scarlet_and_snow, cozy_toon_wrap, bullet_time_lite, magic_cloak, balloon_parade, jumping_ginger_joy, bullet_time, c4d_cartoon_pro, pure_white_wings, black_wings, golden_wing, pink_pink_wings, venomous_spider, throne_of_king, luminous_elf, woodland_elf, japanese_anime_1, american_comics, guardian_spirit, swish_swish, snowboarding, witch_transform, vampire_transform, pumpkin_head_transform, demon_transform, mummy_transform, zombie_transform, cute_pumpkin_transform, cute_ghost_transform, knock_knock_halloween, halloween_escape, baseball, inner_voice, a_list_look, memory_alive, trampoline, trampoline_night, pucker_up, guess_what, feed_mooncake, rampage_ape, flyer, dishwasher, pet_chinese_opera, magic_fireball, gallery_ring, pet_moto_rider, muscle_pet, squeeze_scream, pet_delivery, running_man, disappear, mythic_style, steampunk, 3d_cartoon_2, eagle_snatch, hug_from_past, firework, media_interview, pet_chef, santa_gifts, santa_hug, girlfriend, boyfriend, heart_gesture_1, pet_wizard, smoke_smoke, instant_kid, dollar_rain, cry_cry, building_collapse, gun_shot, mushroom, double_gun, pet_warrior, lightning_power, jesus_hug, shark_alert, long_hair, lie_flat, polar_bear_hug, brown_bear_hug , jazz_jazz, office_escape_plow, fly_fly, watermelon_bomb, pet_dance, boss_coming, wool_curly, pet_bee, marry_me, swing_swing, day_to_night, piggy_morph, wig_out, car_explosion, ski_ski, siblings, construction_worker, let’s_ride, snatched, magic_broom, felt_felt, jumpdrop, splashsplash, surfsurf, fairy_wing, angel_wing, dark_wing, skateskate, plushcut, jelly_press, jelly_slice, jelly_squish, jelly_jiggle, pixelpixel, yearbook, instant_film, anime_figure, rocketrocket, bloombloom, dizzydizzy, fuzzyfuzzy, squish, expansion +更多参数请见: 特效模版中心 +| input | object | 必须 | 无 | 支持不同任务输入的结构体 | +|---|---|---|---|---| +根据scene不同,结构体里传的字段不同,具体如「场景请求体」所示 +| callback_url | string | 可选 | 无 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +场景请求体 +单图特效:220款,flash_drive, shush_my_dreams, advent_of_flora, raid_check, fortune_in_motion, chinese_trend, sedan_chair_dance, yangge_dance, good_luck_dance, laicai_dance, color_mixing, palm_sized_figure, lantern_festival_cuju, unique_firework, unique_spring_couplets, horse_mask, fortune_knocks_cartoon, tangyuan_to_animal, hot_feet_dance, swag_dance, pigeon_dance, bloodline_dance, chanel_dance, cute_dance, love_theme_song, pumpitup_dance, city_to_village, fortune_god_transform, new_year_feast, ring_in_new, horse_year_firework, pet_vlogger, crystal_horse, lateral_shift_transition, drunk_dance, drunk_dance_pet, daoma_dance, bouncy_dance, smooth_sailing_dance, new_year_greeting, lion_dance, prosperity, great_success, golden_horse_fortune, red_packet_box, lucky_horse_year, lucky_red_packet, lucky_money_come, lion_dance_pet, dumpling_making_pet, fish_making_pet, pet_red_packet, lantern_glow, expression_challenge, overdrive, heart_gesture_dance, poping, martial_arts, running, nezha, motorcycle_dance, subject_3_dance, ghost_step_dance, phantom_jewel, zoom_out, dollar_rain_pro, pet_bee_pro, countdown_teleport, santa_random_surprise, magic_match_tree, bullet_time_360, happy_birthday, birthday_star, thumbs_up_pro, tiger_hug_pro, pet_lion_pro, surprise_bouquet, bouquet_drop, 3d_cartoon_1_pro, firework_2026, glamour_photo_shoot, box_of_joy, first_toast_of_the_year, my_santa_pic, santa_gift, steampunk_christmas, snowglobe, christmas_photo_shoot, ornament_crash​, santa_express, instant_christmas, particle_santa_surround, coronation_of_frost, building_sweater, spark_in_the_snow, scarlet_and_snow, cozy_toon_wrap, bullet_time_lite, magic_cloak, balloon_parade, jumping_ginger_joy, bullet_time, c4d_cartoon_pro, pure_white_wings, black_wings, golden_wing, pink_pink_wings, venomous_spider, throne_of_king, luminous_elf, woodland_elf, japanese_anime_1, american_comics, guardian_spirit, swish_swish, snowboarding, witch_transform, vampire_transform, pumpkin_head_transform, demon_transform, mummy_transform, zombie_transform, cute_pumpkin_transform, cute_ghost_transform, knock_knock_halloween, halloween_escape, baseball, inner_voice, a_list_look, memory_alive, trampoline, trampoline_night, pucker_up, guess_what, feed_mooncake, rampage_ape, flyer, dishwasher, pet_chinese_opera, magic_fireball, gallery_ring, pet_moto_rider, muscle_pet, squeeze_scream, pet_delivery, running_man, disappear, mythic_style, steampunk, 3d_cartoon_2, eagle_snatch, hug_from_past, firework, media_interview, pet_chef, santa_gifts, santa_hug, girlfriend, boyfriend, heart_gesture_1, pet_wizard, smoke_smoke, instant_kid, dollar_rain, cry_cry, building_collapse, gun_shot, mushroom, double_gun, pet_warrior, lightning_power, jesus_hug, shark_alert, long_hair, lie_flat, polar_bear_hug, brown_bear_hug , jazz_jazz, office_escape_plow, fly_fly, watermelon_bomb, pet_dance, boss_coming, wool_curly, pet_bee, marry_me, swing_swing, day_to_night, piggy_morph, wig_out, car_explosion, ski_ski, siblings, construction_worker, let’s_ride, snatched, magic_broom, felt_felt, jumpdrop, splashsplash, surfsurf, fairy_wing, angel_wing, dark_wing, skateskate, plushcut, jelly_press, jelly_slice, jelly_squish, jelly_jiggle, pixelpixel, yearbook, instant_film, anime_figure, rocketrocket, bloombloom, dizzydizzy, fuzzyfuzzy, squish, expansion + +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| effect_scene | string | 必须 | 无 | 场景名称 | +| image | string | 必须 | 无 | 参考图像 | +支持传入图片Base64编码或图片URL(确保可访问) +请注意,若您使用base64的方式,请确保您传递的所有图像数据参数均采用Base64编码格式。在提交数据时,请不要在Base64编码字符串前添加任何前缀,例如data:image/png;base64,。正确的参数格式应该直接是Base64编码后的字符串。 +示例: +正确的Base64编码参数: + +错误的Base64编码参数(包含data:前缀): + +请仅提供Base64编码的字符串部分,以便系统能够正确处理和解析您的数据。 +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px,图片宽高比介于1:2.5 ~ 2.5:1之间 +单人特效请求示例 + + +双人互动特效:10款,french_elegance, finger_swipe, smooth_transition, snow_night_kiss, eternal_kiss, cheers_2026, kiss_pro, fight_pro, hug_pro, heart_gesture_pro + +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| effect_scene | string | 必须 | 无 | 场景名称 | +| images | Array[string] | 必须 | 无 | 参考图像组 | +数组的长度必须是2,上传的第一张图在合照的左边,上传的第二张图在合照的右边 +该服务包含合照功能,即用户上传两张人想图,可灵AI将自适应拼接为合照,如图所示先后上传 +"https://p2-kling.klingai.com/bs2/upload-ylab-stunt/c54e463c95816d959602f1f2541c62b2.png?x-kcdn-pid=112452", +"https://p2-kling.klingai.com/bs2/upload-ylab-stunt/5eef15e03a70e1fa80732808a2f50f3f.png?x-kcdn-pid=112452" +得到合照的效果为: + +支持传入图片Base64编码或图片URL(确保可访问) +请注意,若您使用base64的方式,请确保您传递的所有图像数据参数均采用Base64编码格式。在提交数据时,请不要在Base64编码字符串前添加任何前缀,例如data:image/png;base64,。正确的参数格式应该直接是Base64编码后的字符串。 +示例: +正确的Base64编码参数: + +错误的Base64编码参数(包含data:前缀): + +请仅提供Base64编码的字符串部分,以便系统能够正确处理和解析您的数据。 +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px,图片宽高比介于1:2.5 ~ 2.5:1之间 + +双人特效请求示例 (每个特效的请求示例详见:特效模版中心) + + +响应体 + + +3-35【视频特效】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/effects/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 可选 | 无 | 视频特效的任务ID | +请求路径参数,直接将值填写在请求路径中,与external_task_id两种查询方式二选一 +| external_task_id | string | 可选 | 无 | 视频特效的自定义任务ID | +|---|---|---|---|---| +创建任务时填写的external_task_id,与task_id两种查询方式二选一 +请求体 +无 +响应体 + + +3-36【视频特效】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/effects | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +/v1/videos/image2video?pageNum=1&pageSize=30 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + +3-37 【文生音效】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/audio/text-to-audio | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| prompt | string | 必须 | 无 | 文本提示词 | +内容长度不超过200字符 +| duration | float | 必须 | 无 | 生成音频的时长 | +|---|---|---|---|---| +取值范围: ​​3.0秒至10.0秒​​,支持小数点后一位精度 +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 +| callback_url | string | 可选 | 无 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” + +响应体 + + +3-38【文生音效】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/audio/text-to-audio/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 可选 | 无 | 文生音频的任务ID | +请求路径参数,直接将值填写在请求路径中 +| external_task_id | string | 可选 | 无 | 用户自定义任务ID | +|---|---|---|---|---| +创建任务时填写的external_task_id,与task_id两种查询方式二选一 +请求体 +无 +响应体 + + +3-39【文生音效】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/audio/text-to-audio | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +/v1/audio/text-to-audio?pageNum=1&pageSize=30 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + +3-40【视频生音效】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/audio/video-to-audio | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| video_id | string | 必须 | 无 | 通过可灵AI生成的视频的ID | +与video_url参数二选一填写,不能同时为空,也不能同时有值 +仅支持30天内生成并且长度在3.0秒-20.0秒的视频 +| video_url | +|---| + +string +必须 +无 +所上传视频的获取链接 +与video_id参数二选一填写,不能同时为空,也不能同时有值 +视频格式仅支持MP4/MOV,文件大小≤100M,视频长度在3.0秒-20.0秒 +| sound_effect_prompt | string | 可选 | 无 | 音效生成提示词 | +|---|---|---|---|---| +不能超过200个字符 +| bgm_prompt | string | 可选 | 无 | 配乐生成提示词 | +|---|---|---|---|---| +不能超过200个字符 +| asmr_mode | boolean | 可选 | false | 是否开启ASMR模式;该模式会增强细节音效, 适合高沉浸内容场景 | +|---|---|---|---|---| +true表示开启,false表示关闭(默认值) +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 +| callback_url | string | 可选 | 无 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” + +响应体 + + +3-41【视频生音效】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/audio/video-to-audio/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 必须 | 无 | 视频生音频的任务ID | +请求路径参数,直接将值填写在请求路径中 +| external_task_id | string | 可选 | 无 | 用户自定义任务ID | +|---|---|---|---|---| +创建任务时填写的external_task_id,与task_id两种查询方式二选一 +请求体 +无 +响应体 + + +3-42【视频生音效】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/audio/video-to-audio | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +/v1/audio/video-to-audio?pageNum=1&pageSize=30 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + +3-43【通用】语音合成 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/audio/tts | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| text | string | 必填 | 无 | 合成音频的文案 | +文本内容最大长度1000,内容过长会返回错误码等信息 +系统会校验文本内容,如有问题会返回错误码等信息 +| voice_id | string | 必填 | 无 | 音色ID | +|---|---|---|---|---| +系统提供多种音色可供选择,具体音色效果、音色ID、音色语种对应关系点此查看;音色试听不支持自定义文案 +音色试听文件命名规范:音色名称#音色ID#音色语种 +| voice_language | string | 必填 | 无 | 音色语种,与音色ID对应,详见 | +|---|---|---|---|---| +枚举值:zh,en +音色语种与音色ID对应,详见上文 +| voice_speed | float | 可选 | 1.0 | 语速 | +|---|---|---|---|---| +有效范围:0.8~2.0,精确至小数点后1位,超出部分将自动四舍五入 + +响应体 + + +3-44【通用】图像识别 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/videos/image-recognize | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| image | string | 必须 | 无 | 待识别的图片 | +支持传入图片Base64编码或图片URL(确保可访问) +请注意,若您使用base64的方式,请确保您传递的所有图像数据参数均采用Base64编码格式。在提交数据时,请不要在Base64编码字符串前添加任何前缀,例如data:image/png;base64,。正确的参数格式应该直接是Base64编码后的字符串。 +示例: +正确的Base64编码参数: + +错误的Base64编码参数(包含data:前缀): + +请仅提供Base64编码的字符串部分,以便系统能够正确处理和解析您的数据。 +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px,图片宽高比介于1:2.5 ~ 2.5:1之间 + +响应体 + + +3-45【通用】创建主体 +创建主体相关服务已升级至全新版本,如需浏览旧版请移步可灵AI【旧版】主体相关API文档 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/general/advanced-custom-elements | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| element_name | string | 必须 | 无 | 主体名称 | +不能超过20个字符 +| element_description | string | 必须 | 无 | 主体描述 | +|---|---|---|---|---| +不能超过100个字符 +| reference_type | string | 必须 | 无 | 主体参考方式 | +|---|---|---|---|---| +枚举值:video_refer, image_refer +video_refer: 视频角色主体,此时将参考element_video_list定义主体外表 +image_refer: 多图主体,此时将参考element_image_list定义主体外表 +通过视频定制的主体和通过图片定制的主体的可用范围不同,详见能力地图和参数说明。 +| element_image_list | array | 可选 | 空 | 主体参考图,可通过多张图片设定主体及其细节 | +|---|---|---|---|---| +包括正面参考图和其他角度或特写参考图,其中: +至少包括1张正面参考图,由frontal_image参数定义 +需包括1~3张其他参考图,需与正面参考图有差异,由image_url参数定义 +用key:value承载,如下: + +```json +[ + { + "image_url": "https://example.com/image.jpg" + } +] +``` + +支持传入图片Base64编码或图片URL(确保可访问) +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px,图片宽高比要在1:2.5 ~ 2.5:1之间 +reference_type参数值为image_refer时,当前参数必填 +| element_video_list | array | 可选 | 空 | 主体参考视频,可通过视频设定主体及其细节 | +|---|---|---|---|---| +可上传有声视频,有声视频包含人声则触发音色定制(定制+入音色库+与主体绑定) +暂时仅支持通过视频定制写实风格的人形形象 +参考视频时当前参数必填,参考图片时当前参数无效 +用key:value承载,如下: + +```json +[ + { + "video_url": "https://example.com/video.mp4", + "refer_type": "feature", + "keep_original_sound": "yes" + } +] +``` + +视频格式仅支持MP4/MOV +仅支持时长介于3s~8s之间、宽高比例需为16:9或9:16的1080P视频 +至多仅支持上传1段视频,视频大小不超过200MB +video_url参数值不得为空 +视频定制的主体仅支持用于kling-video-o3及之后的模型 +| element_voice_id | string | 可选 | 空 | 主体音色ID,可绑定音色库中已有音色 | +|---|---|---|---|---| +当前参数为空时,当前主体不绑定音色 +为多图主体绑定音色时,仅支持人物形象主体或类人形象主体 +可通过音色相关API获取ID,详见:「可灵AI」新系统 API 接口文档 +| tag_list | array | 可选 | 空 | 为主体配置标签,一个主体可以配置多个标签 | +|---|---|---|---|---| +用key:value承载,其中具体如下: + +tag的ID与名称关系: +| ID | 名称 | +|---|---| +| o_101 | 热梗 | +| o_102 | 人物 | +| o_103 | 动物 | +| o_104 | 道具 | +| o_105 | 服饰 | +| o_106 | 场景 | +| o_107 | 特效 | +| o_108 | 其他 | + +| callback_url | string | 可选 | 无 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + +调用示例 +创建图片定制主体 + +创建视频定制主体 + + +3-46【通用】查询自定义主体(单个) +查询主体相关服务已升级至全新版本,如需浏览旧版请移步可灵AI【旧版】主体相关API文档 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/general/advanced-custom-elements/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 必须 | 无 | 图片生成的任务ID | +请求路径参数,直接将值填写在请求路径中 +| external_task_id | string | 可选 | 空 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +请求体 +无 +响应体 + +调用示例 +查询某个自定义主体 + + +3-47【通用】查询自定义主体(列表) +查询主体相关服务已升级至全新版本,如需浏览旧版请移步可灵AI【旧版】主体相关API文档 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/general/advanced-custom-elements | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + +调用示例 +批量查询自定义主体 + + +3-48【通用】查询官方主体(列表) +查询主体相关服务已升级至全新版本,如需浏览旧版请移步可灵AI【旧版】主体相关API文档 + +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/general/advanced-presets-elements | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + +3-49【通用】删除自定义主体 +删除自定义主体相关服务已原地升级,无需移步其他文档 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/general/delete-elements | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 + +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| element_id | string | 必须 | 无 | 要删除的主体ID,仅支持删除自定义主体 | +响应体 + + +3-50【通用】创建自定义音色 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/general/custom-voices | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| voice_name | string | 必须 | 无 | 音色名称 | +文本内容最大长度20个字符 +创建后不再使用的音色可通过API删除 +| voice_url | string | 可选 | 空 | 音色数据文件获取链接 | +|---|---|---|---|---| +支持.mp3/.wav/.mp4/.mov格式的音视频文件 +音频中人生需干净无杂音,有且只能有一种人声,时长不短于5秒且不长于30秒 +| video_id | string | 可选 | 空 | 历史作品ID,可通过引用历史作品提供音频素材 | +|---|---|---|---|---| +仅满足以下条件的视频可以用于定制音色: +使用V2.6版本模型生成且开启sound参数值为on的视频 +通过数字人API生成的视频 +通过对口型API生成的视频 +音频中人生需干净无杂音,有且只能有一种人声,时长不短于5秒且不长于30秒 +| callback_url | string | 可选 | 空 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 空 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + + +3-51【通用】查询自定义音色(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/general/custom-voices/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 必须 | 无 | 生成音色的任务ID | +请求路径参数,直接将值填写在请求路径中 +| external_task_id | string | 可选 | 空 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 +响应体 + + +3-52 【通用】查询自定义音色(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/general/custom-voices | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,1000] +响应体 + + +3-53【通用】查询官方音色(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/general/presets-voices | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,1000] + +请求体 +无 +响应体 + + +3-54【通用】删除自定义音色 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/general/delete-voices | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| voice_id | string | 必须 | 无 | 待删除的音色的ID,仅支持删除自定义音色 | +响应体 + + +四、虚拟试穿 +4-1【虚拟试穿】创建任务 +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/kolors-virtual-try-on | +| 请求方法 | POST | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | + +请求体 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| model_name | string | 可选 | kolors-virtual-try-on-v1 | 模型名称 | +枚举值:kolors-virtual-try-on-v1, kolors-virtual-try-on-v1-5 +| human_image | string | 必须 | 无 | 上传的人物图片 | +|---|---|---|---|---| +支持传入图片Base64编码或图片URL(确保可访问) +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px +| cloth_image | string | 必须 | 无 | 虚拟试穿的服饰图片 | +|---|---|---|---|---| +支持上传服饰商品图或服饰白底图,支持上装upper、下装lower、与连体装dress +支持传入图片Base64编码或图片URL(确保可访问) +图片格式支持.jpg / .jpeg / .png +图片文件大小不能超过10MB,图片宽高尺寸不小于300px +其中 kolors-virtual-try-on-v1-5 模型不仅支持单个服装输入,还支持“上装+下装”形式服装组合输入,即: +输入单个服饰图片(上装 or 下装 or 连体装)-> 生成试穿的单品图片 +输入组合服饰图片(您可以将多个单品服饰白底图拼接到同一张图片) +模型检测为“上装+下装” -> 生成试穿的“上装+下装”图片 +模型检测为“上装+上装” -> 生成失败 +模型检测为“下装+下装” -> 生成失败 +模型检测为“连体装+连体装” -> 生成失败 +模型检测为“上装+连体装” -> 生成失败 +模型检测为“下装+连体装” -> 生成失败 +组合服饰图片示例:* +| callback_url | string | 可选 | 无 | 本次任务结果回调通知地址,如果配置,服务端会在任务状态发生变更时主动通知 | +|---|---|---|---|---| +具体通知的消息schema见“Callback协议” +| external_task_id | string | 可选 | 无 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 + +响应体 + + +4-2【虚拟试穿】查询任务(单个) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/kolors-virtual-try-on/{id} | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| task_id | string | 必须 | 无 | 虚拟试穿的任务ID | +请求路径参数,直接将值填写在请求路径中 +| external_task_id | string | 可选 | 空 | 自定义任务ID | +|---|---|---|---|---| +用户自定义任务ID,传入不会覆盖系统生成的任务ID,但支持通过该ID进行任务查询 +请注意,单用户下需要保证唯一性 +请求体 +无 +响应体 + + +4-3【虚拟试穿】查询任务(列表) +| 网络协议 | https | +|---|---| +| 请求地址 | /v1/images/kolors-virtual-try-on | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +查询参数 +/v1/images/kolors-virtual-try-on?pageNum=1&pageSize=30 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| pageNum | int | 可选 | 1 | 页码 | +取值范围:[1,1000] +| pageSize | int | 可选 | 30 | 每页数据量 | +|---|---|---|---|---| +取值范围:[1,500] +请求体 +无 +响应体 + + + +五、Callback协议 +对于异步任务(图像生成 / 视频生成 / 虚拟试穿),若您在创建任务时主动设置了callback_url,则当任务状态发生变更时、服务端会主动通知,协议如下 + + +六、账号信息查询 +6-1 查询账号下资源包列表及余量 +注:该接口免费调用,方便您查询账号下的资源包列表和余量,但请您注意控制请求速率(QPS<=1) +| 网络协议 | https | +|---|---| +| 请求地址 | /account/costs | +| 请求方法 | GET | +| 请求格式 | application/json | +| 响应格式 | application/json | + +请求头 +| 字段 | 值 | 描述 | +|---|---|---| +| Content-Type | application/json | 数据交换格式 | +| Authorization | 鉴权信息,参考接口鉴权 | 鉴权信息,参考接口鉴权 | +请求路径参数 +| 字段 | 类型 | 必填 | 默认值 | 描述 | +|---|---|---|---|---| +| start_time | int | 是 | 无 | 查询的开始时间,Unix时间戳、单位ms | +| end_time | int | 是 | 无 | 查询的结束时间,Unix时间戳、单位ms | +| resource_pack_name | string | 否 | 无 | 资源包名称,用于精准指定查询某个资源包 | +请求体 +无 +响应体 + + +七、计费说明 +计费方式 +目前仅提供购买预付费资源包的形式 +目前资源包按照 “能力” 分为三类:视频生成资源包、图像生成资源包、虚拟试穿资源包。 + +积分扣减数量说明 +生成失败的任务不扣积分(任何原因导致的失败均不扣,包含因内容风控策略导致的失败) +图像生成资源包 +| 积分扣减数量说明 | | | +|---|---|---| +| 单张图片规格 | 资源包扣减 | 单价(原价) | +| 【可图Image-O1模型】文生图 | 从资源包总数里扣减8 | 0.2元 | +| 【可图Image-O1模型】图生图 | 从资源包总数里扣减8 | 0.2元 | +| 【可图Image-O1模型】图片编辑 | 从资源包总数里扣减8 | 0.2元 | +| 【可图Image-3O模型】 1K/2K | 从资源包总数里扣减8 | 0.2元 | +| 【可图Image-3O模型】 4K | 从资源包总数里扣减16 | 0.4元 | +| 【可图V1.0模型】文生图 | 从资源包总数里扣减1 | 0.025元 | +| 【可图V1.0模型】图生图 | 从资源包总数里扣减1 | 0.025元 | +| 【可图V1.5模型】文生图 | 从资源包总数里扣减4 | 0.1元 | +| 【可图V1.5模型】图生图 | 从资源包总数里扣减8 | 0.2元 | +| 【可图V2.0模型】文生图 | 从资源包总数里扣减4 | 0.1元 | +| 【可图V2.0模型】图生图 | 从资源包总数里扣减8 | 0.2元 | +| 【可图V2.0-new模型】图生图 | 从资源包总数里扣减8 | 0.2元 | +| 【可图V2.0模型】多图参考生图 | 从资源包总数里扣减16 | 0.4元 | +| 【可图V2.1模型】文生图 | 从资源包总数里扣减4 | 0.1元 | +| 【可图V2.1模型】图生图 | 从资源包总数里扣减8 | 0.2元 | +| 【可图V2.1模型】多图参考生图 | 从资源包总数里扣减16 | 0.4元 | +| 【可图V3.0模型】1K/2K | 从资源包总数里扣减8 | 0.2元 | +| 【图片编辑】扩图 | 从资源包总数里扣减8 | 0.2元 | +| 【智能补全主体图】按服务访问次数计费 | 从资源包总数里扣减20 | 0.5元 | + + +视频生成资源包 +| 积分扣减数量说明 | | | +|---|---|---| +| 单条视频规格 | 资源包扣减 | 单价(原价) | +| 【可灵Video-O1模型】标准(std)x 1s时长 x 无参考视频 | 从资源包总数里扣减0.6 | 0.6元 | +| 【可灵Video-O1模型】标准(std)x 1s时长 x 有参考视频 | 从资源包总数里扣减0.9 | 0.9元 | +| 【可灵Video-O1模型】高品质(pro)x 1s时长 x 无参考视频 | 从资源包总数里扣减0.8 | 0.8元 | +| 【可灵Video-O1模型】高品质(pro)x 1s时长 x 有参考视频 | 从资源包总数里扣减1.2 | 1.2元 | +| 【可灵V3-Omni模型】标准(std)x 1s时长 x 无参考视频 x 无声 | 从资源包总数里扣减0.6 | 0.6元 | +| 【可灵V3-Omni模型】标准(std)x 1s时长 x 无参考视频 x 有声 | 从资源包总数里扣减0.8 | 0.8元 | +| 【可灵V3-Omni模型】标准(std)x 1s时长 x 有参考视频 x 无声 | 从资源包总数里扣减0.9 | 0.9元 | +| 【可灵V3-Omni模型】高品质(pro)x 1s时长 x 无参考视频 x 无声 | 从资源包总数里扣减0.8 | 0.8元 | +| 【可灵V3-Omni模型】高品质(pro)x 1s时长 x 无参考视频 x 有声 | 从资源包总数里扣减1.0 | 1.0元 | +| 【可灵V3-Omni模型】高品质(pro)x 1s时长 x 有参考视频 x 无声 | 从资源包总数里扣减1.2 | 1.2元 | +| 【可灵V1模型】标准(std)x 5s时长 | 从资源包总数里扣减1 | 1元 | +| 【可灵V1模型】标准(std)x 10s时长 | 从资源包总数里扣减2 | 2元 | +| 【可灵V1模型】高品质(pro)x 5s时长 | 从资源包总数里扣减3.5 | 3.5元 | +| 【可灵V1模型】高品质(pro)x 10s时长 | 从资源包总数里扣减7 | 7元 | +| 【可灵V1.5模型】标准(std)x 5s时长 | 从资源包总数里扣减2 | 2元 | +| 【可灵V1.5模型】标准(std)x 10s时长 | 从资源包总数里扣减4 | 4元 | +| 【可灵V1.5模型】高品质(pro)x 5s时长 | 从资源包总数里扣减3.5 | 3.5元 | +| 【可灵V1.5模型】高品质(pro)x 10s时长 | 从资源包总数里扣减7 | 7元 | +| 【可灵V1.6模型】标准(std)x 5s时长 | 从资源包总数里扣减2 | 2元 | +| 【可灵V1.6模型】标准(std)x 10s时长 | 从资源包总数里扣减4 | 4元 | +| 【可灵V1.6模型】高品质(pro)x 5s时长 | 从资源包总数里扣减3.5 | 3.5元 | +| 【可灵V1.6模型】高品质(pro)x 10s时长 | 从资源包总数里扣减7 | 7元 | +| 【可灵V1.6多图参考生视频】标准(std)x 5s时长 | 从资源包总数里扣减2 | 2元 | +| 【可灵V1.6多图参考生视频】标准(std)x 10s时长 | 从资源包总数里扣减4 | 4元 | +| 【可灵V1.6多图参考生视频】高品质(pro)x 5s时长 | 从资源包总数里扣减3.5 | 3.5元 | +| 【可灵V1.6多图参考生视频】高品质(pro)x 10s时长 | 从资源包总数里扣减7 | 7元 | +| 【可灵V2.0大师版模型】x 5s时长 | 从资源包总数里扣减10 | 10元 | +| 【可灵V2.0大师版模型】x 10s时长 | 从资源包总数里扣减20 | 20元 | +| 【可灵V2.1模型】标准(std)x 5s时长 | 从资源包总数里扣减2 | 2元 | +| 【可灵V2.1模型】标准(std)x 10s时长 | 从资源包总数里扣减4 | 4元 | +| 【可灵V2.1模型】高品质(pro)x 5s时长 | 从资源包总数里扣减3.5 | 3.5元 | +| 【可灵V2.1模型】高品质(pro)x 10s时长 | 从资源包总数里扣减7 | 7元 | +| 【可灵V2.1大师版模型】x 5s时长 | 从资源包总数里扣减10 | 10元 | +| 【可灵V2.1大师版模型】x 10s时长 | 从资源包总数里扣减20 | 20元 | +| 【可灵V2.5 turbo模型】标准(std)x 5s时长 | 从资源包总数里扣减1.5 | 1.5元 | +| 【可灵V2.5 turbo模型】标准(std)x 10s时长 | 从资源包总数里扣减3 | 3元 | +| 【可灵V2.5 turbo模型】高品质(pro)x 5s时长 | 从资源包总数里扣减2.5 | 2.5元 | +| 【可灵V2.5 turbo模型】高品质(pro)x 10s时长 | 从资源包总数里扣减5 | 5元 | +| 【可灵V2.6模型】标准(std)x 5s时长 x 无声 x 未指定音色 | 从资源包总数里扣减1.5 | 1.5元 | +| 【可灵V2.6模型】标准(std)x 10s时长 x 无声 x 未指定音色 | 从资源包总数里扣减3 | 3元 | +| 【可灵V2.6模型】高品质(pro)x 5s时长 x 无声 x 未指定音色 | 从资源包总数里扣减2.5 | 2.5元 | +| 【可灵V2.6模型】高品质(pro)x 10s时长 x 无声 x 未指定音色 | 从资源包总数里扣减5 | 5元 | +| 【可灵V2.6模型】高品质(pro)x 5s时长 x 有声 x 未指定音色 | 从资源包总数里扣减5 | 5元 | +| 【可灵V2.6模型】高品质(pro)x 10s时长 x 有声 x 未指定音色 | 从资源包总数里扣减10 | 10元 | +| 【可灵V2.6模型】高品质(pro)x 5s时长 x 有声 x 有指定音色 | 从资源包总数里扣减6 | 6元 | +| 【可灵V2.6模型】高品质(pro)x 10s时长 x 有声 x 有指定音色 | 从资源包总数里扣减12 | 12元 | +| 【可灵V3.0模型】标准(std)x 1s时长 x 无声 | 从资源包总数里扣减0.6 | 0.6元 | +| 【可灵V3.0模型】标准(std)x 1s时长 x 有声 x 未指定音色 | 从资源包总数里扣减0.9 | 0.9元 | +| 【可灵V3.0模型】高品质(pro)x 1s时长 x 无声 | 从资源包总数里扣减0.8 | 0.8元 | +| 【可灵V3.0模型】高品质(pro)x 1s时长 x 有声 x 未指定音色 | 从资源包总数里扣减1.2 | 1.2元 | +| 【动作控制】可灵V2.6模型_标准(std)x 1s时长 | 从资源包总数里扣减0.5 | 0.5元 | +| 【动作控制】可灵V2.6模型_高品质(pro)x 1s时长 | 从资源包总数里扣减0.8 | 0.8元 | +| 【动作控制】可灵V3.0模型_标准(std)x 1s时长 | 从资源包总数里扣减0.9 | 0.9元 | +| 【动作控制】可灵V3.0模型_高品质(pro)x 1s时长 | 从资源包总数里扣减1.2 | 1.2元 | +| 【多模态视频编辑】可灵V1.6模型_标准(std)x 5s时长 | 从资源包总数里扣减3 | 3元 | +| 【多模态视频编辑】可灵V1.6模型_标准(std)x 10s时长 | 从资源包总数里扣减6 | 6元 | +| 【多模态视频编辑】可灵V1.6模型_高品质(pro)x 5s时长 | 从资源包总数里扣减5 | 5元 | +| 【多模态视频编辑】可灵V1.6模型_高品质(pro)x 10s时长 | 从资源包总数里扣减10 | 10元 | +| 【视频延长】可灵V1模型_标准(std) x 4~5s时长 | 从资源包总数里扣减1 | 1元 | +| 【视频延长】可灵V1模型_高品质(pro) x 4~5s时长 | 从资源包总数里扣减3.5 | 3.5元 | +| 【视频延长】可灵V1.5模型_标准(std) x 4~5s时长 | 从资源包总数里扣减2 | 2元 | +| 【视频延长】可灵V1.5模型_高品质(pro) x 4~5s时长 | 从资源包总数里扣减3.5 | 3.5元 | +| 【视频延长】可灵V1.6模型_标准(std) x 4~5s时长 | 从资源包总数里扣减2 | 2元 | +| 【视频延长】可灵V1.6模型_高品质(pro) x 4~5s时长 | 从资源包总数里扣减3.5 | 3.5元 | +| 【数字人】标准(std)x 按时长收费,以秒为单位,四舍五入取整 | 每秒从资源包总数里扣减0.4积分 | 0.4元 | +| 【数字人】高品质(pro)x 按时长收费,以秒为单位,四舍五入取整 | 每秒从资源包总数里扣减0.8积分 | 0.8元 | +| 【对口型】 与视频时长相关,不足5秒按5秒计算 | 每5秒从资源包总数里扣减0.5积分 | 0.5元 | +| 【特效模板】与模版相关,每个特效模板费用不同 | 详见:特效价目表 | 详见:特效价目表 | +| 【视频配音效】可灵音频模型 x 3~20s时长 | 从资源包总数里扣减0.25 | 0.25元 | +| 【文生音效】可灵音频模型 x 3~10s时长 | 从资源包总数里扣减0.25 | 0.25元 | +| 【人脸识别】按服务访问次数计费 | 每次从资源包总数里扣减0.05积分 | 0.05元 | +| 【语音合成】按服务访问次数计费 | 每次从资源包总数里扣减0.05积分 | 0.05元 | +| 【图像识别】按服务访问次数计费,一次访问可得图片中所有类型元素的识别结果 | 每次从资源包总数里扣减0.1积分 | 0.1元 | +| 【音色定制】按调用次数计费 | 每次从资源包总数里扣减0.05积分 | 0.05元 | + + +虚拟试穿资源包 +| 积分扣减数量说明 | | | +|---|---|---| +| 单张图片规格 | 资源包扣减 | 单价(原价) | +| 【可图-虚拟试穿V1模型】 | 从资源包总数里扣减1 | 0.5元 | +| 【可图-虚拟试穿V1.5模型】 | 从资源包总数里扣减1 | 0.5元 | diff --git a/docs/meijiaka-zhijian-final-plan.md b/docs/meijiaka-zhijian-final-plan.md new file mode 100644 index 0000000..cb5a059 --- /dev/null +++ b/docs/meijiaka-zhijian-final-plan.md @@ -0,0 +1,101 @@ +# 美家卡-智剪 (Meijiaka Smart Cut) 项目开发实施方案 + +基于您的最新反馈与确认,本项目将以《golden-purring-crown.md》(方案A)为主要交互蓝本进行落地,明确采用 **手动匹配分镜视频**、**完全本地化数据存储** 和 **沿用现有架构新建仓库** 的策略。 + +## 目标与改动背景 + +**项目背景**:衍生自现有的「美家卡智影」,新项目「美家卡智剪」侧重针对用户已有的视频素材,利用 AI 进行配音、并完成拼接与后期制作。 + +核心工作流程(6 步): +1. **脚本生成** (基于主题生成具有预估时长的分镜与旁白) +2. **视频剪辑 (新)** (用户手动为**每一个单分镜**导入对应长度的视频素材短片) +3. **音色配音 (新)** (用户本地维护音色特征,使用大模型 TTS 为所有分镜批量生成口播音频) +4. **字幕压制** (自动打轴并挂载 ASS 字幕,复用智影功能) +5. **封面制作** (根据首分镜首帧和文字生成封面,复用智影功能) +6. **视频合成** (所有片段首尾拼接成短视频,并将原有环境音替换/混音为合成音频,复用智影功能) + +--- + +## 核心设计决策 (User Confirmed) + +1. **交互模式**:不采用长视频自动切割算法。**必须采用单一分镜独立手动导入视频的交互**。 +2. **数据存储**:**纯本地文件系统**。所有业务数据(项目元数据、分镜配置、克隆好的本地音色记录等)全部保存在用户本地磁盘路径下,**不保存在云端数据库中**。 +3. **架构剥离**:通过拷贝文件系统级别进行剥离 (`rsync ai-meijiaka -> meijiaka-zj`),保留现有混合路由和本地缓存设计。 + +--- + +## Proposed Changes + +### 1. 架构剥离与仓库初始化 +在同级目录下快速搭建衍生仓库,移除不相干的缓存依赖。 + +#### [NEW] `meijiaka-zj/` (新建本地项目根目录) +- 配置应用标识词修正( `产品名: 美家卡智剪`, `Bundle Identifier: cn.meijiaka.ai-video-editor` 等)。 +- 修改并初始化 git 记录。 + +--- + +### 2. 后端 API (Python FastAPI) + +不再新建数据库表结构,将所有新 API 的核心转为与本地文件、大语言模型 API 之间的交互代理,由 AsyncEngine 发起。 + +#### [NEW] `python-api/app/scheduler/handlers/tts_handler.py` +创建用于处理批量语音生成的并发 Dispatcher。 + +#### [NEW] `python-api/app/services/voice_clone_service.py` & `tts_service.py` +包装调用 `KlingAIProvider`: +- 创建克隆音色的调用逻辑(由于无数据库,云端成功后的声纹特征及 `voice_id` 将通过 API 抛回前端并由 Tauri 存入本地 JSON 集合中)。 +- 提供语音合成和查询能力的端点。 + +#### [NEW] `python-api/app/api/v1/voice.py` +仅暴露无状态/代理转发类型的路由给前端:克隆状态查询、提交合成等。无 DB 依赖。 + +--- + +### 3. Rust 系统能力扩展 (src-tauri) + +由于采用本地存储,需要在 Rust 层扩展音频文件和声纹文件的安全存储指令。 + +#### [NEW] `tauri-app/src-tauri/src/storage/voice.rs` +新增声音本地缓存与描述管理,目录范例:`~/Documents/Meijiaka/voices/` (用于存储 voice meta.json 和相关的 reference audio)。 + +#### [NEW] `tauri-app/src-tauri/src/commands/voice.rs` +由 Tauri 提供存储IPC API给前端:读取本地音色列表、写入新克隆的音色等。 + +#### [MODIFY] `tauri-app/src-tauri/src/ffmpeg_cmd.rs` +**[重要机制更新]**: 实现目标音频覆盖处理,提供类似 `replace_audio_in_video` 的函数,依靠 `-c:v copy -c:a aac -shortest -map 0:v:0 -map 1:a:0` 剥离原声并在对应的短视频片段上压入新的 TTS 朗读声音。 + +--- + +### 4. 前端应用层 (tauri-app / React) + +调整原应用状态数据,创建本地数据绑定。 + +#### [MODIFY] `tauri-app/src/store/projectStore.ts` +扩展原 `SmartCutShot` 阶段参数,支持记录新增加的独立视频源地址 (`mediaPath`) 和单独段落的合成语音地址 (`audioPath`)。 + +#### [NEW] `tauri-app/src/store/voiceStore.ts` +与 `src-tauri` 通过 IPC 交互: +- 从本地加载用户维护在 `voices/` 下的所有自定义音色。 +- 处理前端的缓存与显示。 + +#### [NEW] `tauri-app/src/pages/VideoCreation/VideoEditing.tsx` (Step 2) +重定义分镜视频导入步骤: +- 为左侧每一个生成的分镜文案展示独立的 Upload/Select Box。 +- 用户可以点选或拖动,调用系统弹窗将 `mp4` 一对一绑定给自己心仪的旁白节点。 + +#### [NEW] `tauri-app/src/pages/VideoCreation/VoiceDubbing.tsx` (Step 3) +批量克隆与TTS应用页面: +- 渲染本地和预定义的云端默认音库。 +- 前端批量发起所有含旁白分镜的异步合成任务,获取 URL 后调用 Rust 保留至项目对应的 `audio/` 子目录中。 + +--- + +## Verification Plan + +### Manual Verification (端到端走通测试) +- **环境**: 在新目录 `meijiaka-zj` 启动前后端服务。 +- **Step 1**: 使用纯业务旁白的模版生成分镜文案。 +- **Step 2**: 对列表中独立出现的 3 个分镜卡片,依次上传/拖入 3 个独立的 `.mp4` 文件以测试前端映射逻辑。 +- **Step 3(关键测试)**: 选择一个克隆音色发起全局合成。观察 `tts_slots` 运转状况。完毕后查验对应项目的物理存储路径内正确生成了 `.mp3` 音轨。 +- **Step 6**: 打包合成,测试 `ffmpeg_cmd.rs` 中音频替代逻辑是否执行无误,输出画面不掉帧、声音是合成口音的短片。 diff --git a/docs/meijiaka-zhijian-proposal.md b/docs/meijiaka-zhijian-proposal.md new file mode 100644 index 0000000..c9d7d4f --- /dev/null +++ b/docs/meijiaka-zhijian-proposal.md @@ -0,0 +1,833 @@ +# 美家卡智剪 — 产品技术方案 + +> 基于「美家卡智影」架构的 AI 辅助短视频剪辑产品方案 +> 版本: v2.0 | 日期: 2026-04-20 + +--- + +## 一、产品定位 + +| 维度 | 美家卡智影(现有) | 美家卡智剪(新项目) | +|------|-------------------|---------------------| +| **核心能力** | AI 数字人视频生成 | AI 音色克隆 + 语音合成 + 素材智能剪辑 | +| **视频来源** | KlingAI 生成数字人视频 | 用户导入长视频素材 | +| **声音来源** | KlingAI 预设/自定义音色 + 数字人 | 用户克隆音色 / 预设音色 + TTS | +| **目标场景** | 口播视频、营销视频从无到有 | 已有长素材快速剪辑成片、声音克隆配音 | +| **核心差异** | 「生成式」创作 | 「剪辑式」创作 + AI 声音 | + +### 一句话定义 +> **美家卡智剪** = 导入长视频 + AI 文案分镜 + 自动切割 + 音色克隆 + 语音合成 + 字幕压制 + 封面合成 + 视频导出 + +--- + +## 二、核心流程设计(6 步) + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ │ +│ Step 1 → Step 2 → Step 3 → Step 4 → S5 → S6 │ +│ 脚本生成 → 视频剪辑 → 音色配音 → 字幕压制 → 封面 → 合成 │ +│ │ +│ ├─ AI文案 ├─ 导入长视频 ├─ 音色克隆 ├─ 自动打轴 │ +│ ├─ 粘贴文案 ├─ 自动切割 ├─ 预设音色 ├─ ASS字幕 │ +│ ├─ 智能分镜 │ (按分镜时长) ├─ 分镜TTS ├─ FFmpeg压制 │ +│ │ │ ├─ 试听/调整 │ +│ │ │ │ │ +│ [改造] [全新] [全新] [复用] [复用] │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +### 各步骤详细说明 + +--- + +#### Step 1 — 脚本生成(Script Generation) + +**文案输入(3 种方式):** +1. **AI 生成**:输入主题/关键词,LLM 生成短视频文案 +2. **直接粘贴**:用户粘贴已准备好的文案,系统自动分镜 +3. **导入文件**:支持 `.txt` / `.docx` / `.srt` 导入 + +**智能分镜:** +- 按句子/段落自动拆分分镜 +- 每个分镜含:`voiceover`(旁白文案)、`duration`(预估时长) +- 支持拖拽调整分镜顺序、合并、拆分 +- 文案字数根据目标时长自动约束(15s≈40字 / 30s≈80字 / 60s≈160字) + +**输出:** +- `segments[]`:分镜列表,每个分镜含文案和预估时长 +- 此步骤与智影 Step 1 基本一致,Prompt 调整为生成纯旁白文案(不含场景描述) + +--- + +#### Step 2 — 视频剪辑(Video Editing) + +**核心逻辑:导入一个长视频,按分镜时长自动切割。** + +**流程:** +1. 用户导入一个长视频文件(`.mp4/.mov`) +2. 系统提取视频总时长 +3. 按分镜数量和预估时长自动计算切割点 +4. 调用 FFmpeg 将长视频切割为 N 个片段 +5. 每个片段自动绑定到对应分镜 + +**自动切割算法:** +``` +总视频时长 = T +分镜数 = N +分镜预估时长 = [d1, d2, ..., dN] +预估总时长 = D = d1 + d2 + ... + dN + +如果 D <= T: + 按比例分配: 每个分镜实际时长 = di * (T / D) + 切割点: cumsum([d1*T/D, d2*T/D, ...]) + +如果 D > T: + 提示用户: 文案预估总时长超过视频时长,建议缩短文案或导入更长视频 +``` + +**界面示意:** +``` +┌────────────────────────────────────────────┐ +│ 分镜列表 │ 素材导入 │ +│ ├─ 分镜1 (5s) │ ├─ 📁 点击导入 │ +│ ├─ 分镜2 (8s) │ │ 或拖拽视频 │ +│ ├─ 分镜3 (7s) │ │ │ +│ └─ 分镜4 (5s) │ │ 🎬 素材.mp4 │ +│ │ │ 时长: 25s │ +│ 预估总时长: 25s │ │ 分辨率: 1080p │ +│ │ └────────────────│ +│ [自动切割] │ │ +└────────────────────────────────────────────┘ + +切割结果预览: +┌────────────────────────────────────────────┐ +│ 分镜1 ←→ 🎬 [00:00 - 00:05] (5s) │ +│ 分镜2 ←→ 🎬 [00:05 - 00:13] (8s) │ +│ 分镜3 ←→ 🎬 [00:13 - 00:20] (7s) │ +│ 分镜4 ←→ 🎬 [00:20 - 00:25] (5s) │ +└────────────────────────────────────────────┘ +``` + +**技术实现:** +- 前端:文件选择 → 调用 Rust IPC `import_media` → 保存到项目 `media/` 目录 +- Rust:`split_video` 命令使用 FFmpeg `-ss` + `-t` 截取片段 +- 每个片段保存为 `shot_{index}.mp4`,路径写入 `segment.mediaPath` + +--- + +#### Step 3 — 音色配音(Voice & Dubbing) + +**音色管理:** +- **预设音色**:接入 KlingAI 官方预设音色(温柔女声、播报男声等) +- **我的音色**:用户克隆的音色列表 + - 克隆方式:录音(10-20 秒)或上传音频文件 + - 克隆状态:处理中 / 完成 / 失败 + - 支持预览、重命名、删除 + +**语音合成(TTS):** +- 为每个分镜独立选择音色 +- 支持统一设置(一键应用到全部分镜) +- 可调节语速(0.8x - 2.0x) +- 实时试听、重新生成 + +**批量合成:** +- 一键合成所有分镜音频 +- 后台 Async Engine 并行处理(受槽位限制) +- 实时进度显示 + +--- + +#### Step 4 — 字幕压制(Subtitle Burning) + +**基本复用智影现有逻辑,数据源变化:** +- 原:基于数字人视频的音频流进行自动打轴 +- 新:基于 TTS 合成的音频文件进行自动打轴 + +**流程:** +1. 提交 `subtitle` 任务(`mode: auto_align`) +2. 参数:`audioUrl`(TTS 音频)+ `audioText`(分镜文案) +3. 返回 `alignmentResult`(utterances 时间轴) +4. 用户选择字幕样式(颜色/字号/描边/位置) +5. 调用 Rust IPC `burn_subtitle` 压制 ASS 字幕到视频 + +**输出:** +- 每个分镜生成 `burnedVideoPath`(素材视频 + TTS 音频 + ASS 字幕) + +--- + +#### Step 5 — 封面制作(Cover Design) + +**完全复用智影现有逻辑:** +1. 提取第一个分镜视频的首帧作为背景 +2. 用户输入封面标题 +3. 选择字体样式(抖音美好体等) +4. 调用 Rust IPC `generate_cover_image` 合成封面 + +--- + +#### Step 6 — 视频合成(Video Composite) + +**完全复用智影现有逻辑:** +1. 收集所有分镜的 `burnedVideoPath` +2. 如有封面图,先转为 0.5s 封面视频 +3. 调用 Rust IPC `video_composite_synthesis` 拼接所有片段 +4. 输出最终成品到 `~/Documents/Meijiaka/products/` + +--- + +## 三、功能模块对比矩阵 + +| 模块 | 智影(现有) | 智剪(新) | 复用度 | +|------|-------------|-----------|--------| +| **脚本生成** | AI 生成脚本 | AI 生成文案 + 粘贴/导入 | 🔶 改造 | +| **视频生成** | KlingAI 数字人 | 素材导入 + **自动切割** | 🔴 新增 | +| **音色管理** | KlingAI Element 绑定音色 | 独立音色克隆 + 预设库 | 🔶 改造 | +| **语音合成** | 数字人自带口播 | TTS 独立合成音频 | 🔴 新增 | +| **字幕压制** | 自动打轴+FFmpeg | 完全复用 | 🟢 复用 | +| **封面制作** | 首帧+标题+FFmpeg | 完全复用 | 🟢 复用 | +| **视频合成** | FFmpeg concat | 完全复用 | 🟢 复用 | +| **本地存储** | meta.json + segments.json | 扩展字段 | 🔶 改造 | +| **任务调度** | 6 个 Handler | 新增 TTS Handler | 🔶 改造 | +| **用户认证** | JWT + 手机号 | 完全复用 | 🟢 复用 | +| **形象克隆** | Avatar 完整流程 | 简化为音色克隆 | 🔶 改造 | + +> 🟢 完全复用 | 🔶 需要改造 | 🔴 全新开发 + +--- + +## 四、前端架构方案 + +### 4.1 页面结构 + +``` +tauri-app/src/pages/ +├── VideoCreation/ +│ ├── index.tsx # 6步流程容器(复用,调整步骤名) +│ ├── ScriptCreation.tsx # Step 1: 脚本生成(复用改造) +│ ├── VideoEditing.tsx # Step 2: 视频剪辑(全新) +│ ├── VoiceDubbing.tsx # Step 3: 音色配音(全新) +│ ├── SubtitleBurning.tsx # Step 4: 字幕压制(复用) +│ ├── CoverDesign.tsx # Step 5: 封面制作(复用) +│ └── VideoComposite.tsx # Step 6: 视频合成(复用) +``` + +### 4.2 Store 设计 + +#### projectStore(改造) + +```typescript +interface SmartCutState { + // === Step 1: 脚本与分镜 === + segments: SmartCutShot[]; + topic?: string; + scriptType?: string; + + // === Step 3: 音色配音 === + defaultVoiceId?: string; // 默认音色 + + // === Step 5+6: 封面与合成 === + coverPath?: string; + coverConfig?: CoverConfig; + finalVideoPath?: string; + exportedAt?: string; + + // === 流程状态 === + currentStep: number; // 1-6 +} + +interface SmartCutShot { + id: string; + type: 'segment' | 'empty_shot'; + voiceover: string; // 旁白文案 + duration: number; // 预估/实际时长 + + // === Step 2: 视频剪辑后绑定 === + mediaPath?: string; // 切割后的视频片段路径 + mediaStartTime?: number; // 在原视频中的起始时间(秒) + mediaEndTime?: number; // 在原视频中的结束时间(秒) + + // === Step 3: 配音配置 === + ttsConfig?: TTSConfig; + audioPath?: string; // TTS 合成音频本地路径 + audioUrl?: string; // TTS 音频远程 URL + + // === Step 4: 字幕与后期 === + alignmentResult?: AlignmentResult; + burnedVideoPath?: string; + burnedAt?: string; +} + +interface TTSConfig { + voiceId: string; + voiceName: string; + speed: number; // 0.8 - 2.0 +} +``` + +#### voiceStore(新增) + +```typescript +interface VoiceState { + // 预设音色 + presetVoices: PresetVoice[]; + presetVoicesLoading: boolean; + + // 用户克隆音色 + clonedVoices: ClonedVoice[]; + clonedVoicesLoading: boolean; + + // 当前选中的默认音色 + selectedVoiceId?: string; +} + +interface PresetVoice { + voiceId: string; + voiceName: string; + previewUrl?: string; + provider: string; +} + +interface ClonedVoice { + id: string; // vc_xxx + name: string; + providerVoiceId: string; // KlingAI 返回的 voice_id + provider: string; + status: 'processing' | 'succeed' | 'failed'; + previewUrl?: string; + createdAt: string; +} +``` + +### 4.3 新增 Hooks + +| Hook | 职责 | +|------|------| +| `useVoiceClone.ts` | 音色克隆:提交克隆、轮询状态、管理列表 | +| `useTTSGeneration.ts` | TTS 批量合成:提交任务、轮询、更新 segment | +| `useMediaImport.ts` | 素材导入:文件选择、调用 Rust IPC | +| `useAutoSplit.ts` | 自动切割:计算切割点、调用 split_video、绑定分镜 | + +### 4.4 API 模块 + +``` +tauri-app/src/api/modules/ +├── voice.ts # 音色克隆 / 预设音色 / 查询 / 删除 +├── tts.ts # TTS 提交 / 查询 / 批量 +├── script.ts # 复用,文案生成 +├── caption.ts # 复用,字幕相关 +└── videoComposite.ts # 复用,视频合成 +``` + +--- + +## 五、后端架构方案 + +### 5.1 新增 API 路由 + +```python +# python-api/app/api/v1/voice.py +@router.post("/voice/clone") # 提交音色克隆任务 +@router.get("/voice/clones") # 查询用户克隆音色列表 +@router.get("/voice/clones/{id}") # 查询单个克隆任务 +@router.delete("/voice/clones/{id}") # 删除克隆音色 +@router.get("/voice/presets") # 查询预设音色列表 + +# python-api/app/api/v1/tts.py +@router.post("/tts") # 提交 TTS 任务 +@router.get("/tts/{job_id}") # 查询 TTS 任务状态 +@router.post("/tts/batch") # 批量提交 TTS 任务 +``` + +### 5.2 新增 Async Engine Handler + +新增 **`tts`** 任务类型: + +```python +# app/scheduler/handlers/tts_handler.py + +class TTSHandler(AsyncHandler): + """TTS 语音合成 Handler + + 为每个分镜的文案生成语音音频。 + """ + job_type = "tts" + slot_key = "kling:tts_slots" + max_slots = 10 + + async def handle(self, job: JobRecord) -> list[StateChange]: + """处理流程: + 1. 从 job.payload 提取 text, voice_id, voice_speed + 2. 调用 KlingAI TTS API 生成音频 + 3. 轮询任务完成 + 4. 下载音频文件到本地项目目录 + 5. (可选)上传七牛云持久化 + 6. 返回结果含 audio_path, audio_url, duration + """ +``` + +**Redis 配置:** +``` +槽位 Key: kling:tts_slots +槽位数: 10 +``` + +### 5.3 新增 Service 层 + +```python +# app/services/tts_service.py +class TTSService: + """TTS 语音合成服务""" + + async def generate_audio( + self, + text: str, + voice_id: str, + voice_speed: float = 1.0, + output_dir: str | None = None, + ) -> TTSResult: + """生成单条 TTS 音频""" + + async def batch_generate( + self, + items: list[TTSRequest], + user_id: str, + ) -> list[str]: + """批量提交 TTS 任务到 Async Engine""" + +# app/services/voice_clone_service.py +class VoiceCloneService: + """音色克隆服务""" + + async def create_clone( + self, + voice_name: str, + audio_url: str, # 七牛云音频URL + user_id: str, + ) -> VoiceCloneJob: + """提交音色克隆任务到 KlingAI""" + + async def sync_clone_status( + self, + job_id: str, + ) -> VoiceCloneStatus: + """同步查询克隆任务状态(轻量操作,不走Async Engine)""" + + async def list_clones(self, user_id: str) -> list[ClonedVoice]: + """查询用户所有克隆音色""" +``` + +### 5.4 新增数据库模型 + +```python +# app/models/voice_clone.py +class VoiceClone(Base): + """用户克隆音色元数据(云端备份)""" + __tablename__ = "voice_clones" + + id: Mapped[str] = mapped_column(String(32), primary_key=True) # vc_xxx + user_id: Mapped[UUID] = mapped_column(ForeignKey("users.id")) + name: Mapped[str] = mapped_column(String(100)) + provider: Mapped[str] = mapped_column(String(50), default="klingai") + provider_voice_id: Mapped[str] = mapped_column(String(100)) + status: Mapped[str] = mapped_column(String(20)) # processing/succeed/failed + preview_url: Mapped[str | None] = mapped_column(String(500)) + fail_reason: Mapped[str | None] = mapped_column(Text) + deleted_at: Mapped[datetime | None] + created_at: Mapped[datetime] + updated_at: Mapped[datetime] +``` + +> 注:智剪中不需要 Element(形象主体),只需要 Voice(音色),因此独立建表更简洁。 + +### 5.5 复用已有能力 + +| 已有能力 | 复用方式 | +|---------|---------| +| `KlingAIProvider.generate_tts()` | 直接调用,封装到 Service 层 | +| `KlingAIProvider.create_custom_voice()` | 直接调用,封装到 VoiceCloneService | +| `KlingAIProvider.list_preset_voices()` | 直接调用 | +| `VolcengineCaptionService` | 完全复用,传入 TTS 音频 URL | +| `SlotManager` + `JobRegistry` | 完全复用 | +| `TokenManager` + `JWTTokenStrategy` | 完全复用 | +| `qiniu_service.upload()` | 复用,支持 audio 类型 | +| 七牛云上传凭证 | 复用 | + +--- + +## 六、Rust 层改造方案 + +### 6.1 新增 IPC 命令 + +```rust +// commands/media.rs +#[tauri::command] +async fn import_media( + app: AppHandle, + project_id: String, + source_path: String, +) -> Result + +// commands/video_edit.rs +#[tauri::command] +async fn split_video( + app: AppHandle, + input_path: String, + segments: Vec, // [{start, end, output_name}] +) -> Result, String> // 返回切割后的文件路径列表 +``` + +### 6.2 新增 FFmpeg 命令封装 + +在 `ffmpeg_cmd.rs` 中新增: + +```rust +/// 按时间范围批量截取视频片段 +/// +/// 输入一个长视频,按多个时间范围切割为独立文件 +pub async fn split_video_segments( + app: &AppHandle, + input: &str, + segments: &[(f64, f64, &str)], // (start, end, output_path) +) -> Result, FFmpegError> + +/// 提取视频元信息(时长、分辨率、码率等) +pub async fn probe_media_info( + input: &str, +) -> Result +``` + +### 6.3 本地存储路径扩展 + +```rust +// storage/paths.rs + +/// 项目素材目录:~/Documents/Meijiaka/projects/{id}/media/ +pub fn get_project_media_dir(project_id: &str) -> PathBuf + +/// 项目音频目录:~/Documents/Meijiaka/projects/{id}/audio/ +pub fn get_project_audio_dir(project_id: &str) -> PathBuf + +/// 项目分镜视频目录:~/Documents/Meijiaka/projects/{id}/shots/ +pub fn get_project_shots_dir(project_id: &str) -> PathBuf +``` + +存储结构: +``` +~/Documents/Meijiaka/ +├── projects/{project_id}/ +│ ├── meta.json +│ ├── segments.json +│ ├── media/ # 导入的原始素材 +│ │ └── source.mp4 # 原始长视频 +│ ├── shots/ # 自动切割后的分镜视频 +│ │ ├── shot_001.mp4 +│ │ └── shot_002.mp4 +│ ├── audio/ # TTS 生成的音频 +│ │ ├── tts_001.mp3 +│ │ └── tts_002.mp3 +│ └── assets/ # 封面等成品资源 +│ └── cover_xxx.png +``` + +--- + +## 七、AI 能力集成 + +### 7.1 音色克隆 + +**Provider: KlingAI(已具备能力)** + +``` +API: POST /v1/general/custom-voices +参数: + - voice_name: 音色名称 + - voice_url: 音频文件URL(5-30秒,干净人声) + +限制: + - 音频时长: 5-30 秒 + - 格式: MP3 / WAV + - 要求: 单一人声、无杂音、无背景音乐 +``` + +**前端录音方案:** +- 使用 Web Audio API 录制麦克风音频 +- 实时波形可视化 +- 录制时长控制(10-20 秒最佳) +- 录制完成后上传至七牛云 → 后端提交克隆任务 + +**状态流转:** +``` +用户录音/上传 → 前端上传七牛云 → 后端调用 KlingAI 创建音色 + ↓ + [processing] ← 前端轮询 + ↓ + [succeed] → 保存到 DB → 加入"我的音色" + ↓ + [failed] → 提示用户重新录制 +``` + +### 7.2 语音合成(TTS) + +**Provider: KlingAI(已具备能力,需上层封装)** + +``` +API: POST /v1/audio/tts +参数: + - text: 要合成的文本(旁白文案) + - voice_id: 音色ID(预设或自定义) + - voice_language: zh / en + - voice_speed: 0.8 - 2.0(默认 1.0) + +返回: + - task_id: 任务ID + - 轮询 GET /v1/audio/tts/{task_id} 获取音频URL +``` + +**批量处理策略:** +- 每个分镜一个 TTS 任务 +- Async Engine 并行处理(最多 10 个并发) +- 前端显示总体进度(已完成 N / 总分镜数 M) + +### 7.3 文案生成 + +**复用现有 ScriptService**,但调整 Prompt: +- 原:生成「场景描述 + 旁白 + 时长」的营销脚本 +- 新:生成「旁白文案 + 预估时长」的短视频文案 +- 支持根据目标时长(15s / 30s / 60s)控制字数 + +--- + +## 八、独立新仓库初始化方案 + +### 8.1 仓库创建 + +```bash +# 在本地创建新仓库目录 +mkdir meijiaka-zj +cd meijiaka-zj +git init + +# 复制智影代码(排除依赖和构建产物) +rsync -av \ + --exclude='.git' \ + --exclude='node_modules' \ + --exclude='.venv' \ + --exclude='__pycache__' \ + --exclude='.mypy_cache' \ + --exclude='.ruff_cache' \ + --exclude='.pytest_cache' \ + --exclude='dist' \ + --exclude='target' \ + --exclude='*.lock' \ + --exclude='.DS_Store' \ + ../ai-meijiaka/ . + +# 初始化提交 +git add -A +git commit -m "init: fork from meijiaka-zy" +``` + +### 8.2 品牌配置修改清单 + +| 文件 | 修改项 | +|------|--------| +| `tauri-app/src-tauri/tauri.conf.json` | `productName`: 美家卡智影 → 美家卡智剪;`identifier`: `cn.meijiaka.ai-video` → `cn.meijiaka.ai-video-editor`;`title`: 美家卡 智影 → 美家卡 智剪 | +| `tauri-app/package.json` | `name`: 可保持不变(内部包名) | +| `python-api/app/main.py` | FastAPI 文档标题、描述更新 | +| `AGENTS.md` | 全文替换「智影」→「智剪」,更新产品描述 | +| `README.md` | 更新为智剪的产品说明 | + +### 8.3 项目结构 + +``` +meijiaka-zj/ # 新仓库根目录 +├── python-api/ # FastAPI 后端(从智影复制后改造) +│ ├── app/ +│ │ ├── api/v1/ # 新增 voice.py, tts.py 路由 +│ │ ├── ai/providers/ # 复用 KlingAIProvider +│ │ ├── scheduler/handlers/ # 新增 tts_handler.py +│ │ ├── services/ # 新增 tts_service.py, voice_clone_service.py +│ │ ├── models/ # 新增 voice_clone.py +│ │ └── schemas/ # 新增 voice.py, tts.py +│ ├── config/ +│ ├── alembic/ +│ ├── pyproject.toml +│ └── ... +│ +├── tauri-app/ # Tauri 前端(从智影复制后改造) +│ ├── src/ +│ │ ├── pages/VideoCreation/ +│ │ │ ├── ScriptCreation.tsx # Step 1(改造) +│ │ │ ├── VideoEditing.tsx # Step 2(新增) +│ │ │ ├── VoiceDubbing.tsx # Step 3(新增) +│ │ │ ├── SubtitleBurning.tsx # Step 4(复用) +│ │ │ ├── CoverDesign.tsx # Step 5(复用) +│ │ │ └── VideoComposite.tsx # Step 6(复用) +│ │ ├── store/ +│ │ │ ├── projectStore.ts # 改造 +│ │ │ └── voiceStore.ts # 新增 +│ │ ├── api/modules/ +│ │ │ ├── voice.ts # 新增 +│ │ │ └── tts.ts # 新增 +│ │ └── hooks/ +│ │ ├── useVoiceClone.ts # 新增 +│ │ ├── useTTSGeneration.ts # 新增 +│ │ └── useAutoSplit.ts # 新增 +│ ├── src-tauri/src/ +│ │ ├── commands/media.rs # 新增 +│ │ ├── ffmpeg_cmd.rs # 新增函数 +│ │ └── storage/paths.rs # 新增路径 +│ └── ... +│ +├── docs/ # 文档 +│ └── meijiaka-zhijian-proposal.md +│ +└── scripts/ # 工具脚本 +``` + +--- + +## 九、实施路线图 + +### Phase 1: 基础架构(2 周) + +**目标**:搭建新项目骨架,打通基础能力 + +| 任务 | 说明 | +|------|------| +| ① 仓库初始化 | 复制智影代码,修改品牌配置,建立独立仓库 | +| ② 数据模型改造 | 新增 `voice_clones` 表,改造 `segments` Schema | +| ③ TTS API 封装 | 新增 `tts_service.py`、`voice.py` / `tts.py` 路由 | +| ④ 音色克隆 API | 新增 `voice_clone_service.py` | +| ⑤ 前端 Store 改造 | 改造 `projectStore`,新增 `voiceStore` | +| ⑥ 素材导入 IPC | 新增 `import_media`、`split_video` Rust 命令 | + +### Phase 2: 核心流程(2 周) + +**目标**:完成 6 步核心流程 MVP + +| 任务 | 说明 | +|------|------| +| ⑦ Step 1 脚本生成 | 复用现有逻辑,Prompt 调整为纯旁白文案 | +| ⑧ Step 2 视频剪辑 | 素材导入 UI + 自动切割逻辑 + 分镜绑定 | +| ⑨ Step 3 音色配音 | 音色克隆 UI + TTS 合成 UI + 批量任务 | +| ⑩ TTS Async Handler | 实现 `TTSHandler`,接入 Async Engine | +| ⑪ 字幕压制适配 | 基于 TTS 音频的自动打轴 + 字幕压制 | +| ⑫ 封面+合成 | 复用现有逻辑,验证端到端流程 | + +### Phase 3: 打磨优化(1 周) + +**目标**:提升用户体验,修复问题 + +| 任务 | 说明 | +|------|------| +| ⑬ 切割算法优化 | 智能检测场景切换点,避免在人物说话中间切割 | +| ⑭ 批量操作优化 | 统一音色、批量重新合成 | +| ⑮ 错误处理 | 视频格式不支持、TTS 失败、文案超长等异常 | +| ⑯ 性能优化 | 大视频导入、多任务并发 | +| ⑰ 测试验收 | 全流程测试,修复 bug | + +### 总工期预估:**5 周** + +``` +Week 1-2: Phase 1 — 基础架构 +Week 3-4: Phase 2 — 核心流程 MVP +Week 5: Phase 3 — 打磨优化 + 测试 +``` + +--- + +## 十、技术风险与应对 + +| 风险 | 影响 | 应对方案 | +|------|------|---------| +| KlingAI TTS 并发限制 | 批量合成慢 | Async Engine 槽位控制 + 前端进度管理 | +| KlingAI 音色克隆失败率高 | 用户体验差 | 前端引导用户录制规范音频(安静环境、清晰人声) | +| 文案总时长 > 视频时长 | 无法完整配音 | Step 2 导入时校验,超长则提示用户调整文案或换视频 | +| 自动切割点落在不自然位置 | 画面割裂 | V2 引入场景切换检测,在关键帧处切割 | +| 大视频文件导入卡顿 | 前端无响应 | Tauri 后端异步处理导入,前端仅显示进度 | +| 视频格式兼容性 | 某些格式无法处理 | FFmpeg 统一标准化转码,支持主流格式 | +| TTS 文本过长 | KlingAI 限制 | 分镜文案字数控制(建议单分镜 < 200 字) | + +--- + +## 十一、长期演进方向 + +| 版本 | 功能 | +|------|------| +| **V1.0**(MVP)| 长视频导入 + 自动切割 + 音色克隆 + TTS + 字幕 + 封面 + 合成 | +| **V1.5** | 智能切割(基于场景切换检测) | +| **V2.0** | 多轨道编辑(背景音乐、音效、转场) | +| **V2.5** | AI 视频摘要(长视频自动提取精彩片段) | +| **V3.0** | 多音色对话(支持多人配音、角色音色) | + +--- + +## 附录 + +### A. 关键术语对照 + +| 智影术语 | 智剪对应 | 说明 | +|---------|---------|------| +| `elementId` | `voiceId` | 从数字人形象ID变为音色ID | +| `videoUrl` | `mediaPath` | 从AI生成视频变为切割后的素材片段 | +| `Avatar` | `VoiceClone` | 从形象克隆简化为音色克隆 | +| `humanId` | — | 移除,不再需要 | +| `scene` | — | 可选保留,用于V2智能匹配 | + +### B. 需要改造的文件清单 + +**后端(python-api):** +``` +新增: + app/api/v1/voice.py + app/api/v1/tts.py + app/services/tts_service.py + app/services/voice_clone_service.py + app/scheduler/handlers/tts_handler.py + app/models/voice_clone.py + app/schemas/voice.py + app/schemas/tts.py + +改造: + app/scheduler/main.py # 注册 TTSHandler + app/api/v1/router.py # 添加 voice/tts 路由 + app/schemas/segment.py # 扩展 Segment Schema + app/ai/prompts/script/*.txt # 调整 Prompt 为纯旁白文案 +``` + +**前端(tauri-app):** +``` +新增: + src/pages/VideoCreation/VideoEditing.tsx + src/pages/VideoCreation/VoiceDubbing.tsx + src/store/voiceStore.ts + src/api/modules/voice.ts + src/api/modules/tts.ts + src/hooks/useVoiceClone.ts + src/hooks/useTTSGeneration.ts + src/hooks/useAutoSplit.ts + +改造: + src/pages/VideoCreation/index.tsx # 调整为6步 + src/pages/VideoCreation/ScriptCreation.tsx # 移除场景描述字段 + src/store/projectStore.ts # 扩展数据模型 + src/api/types.ts # 更新类型定义 +``` + +**Rust(src-tauri):** +``` +新增: + src/commands/media.rs + src/ffmpeg_cmd.rs 中的 split_video_segments / probe_media_info + src/storage/paths.rs 中的 media/audio/shots 路径 + +改造: + src/lib.rs # 注册新命令 +``` + +--- + +*本方案基于「美家卡智影」现有架构设计,最大化复用已有能力,降低开发成本与风险。* diff --git a/docs/migrate-avatars-to-local.md b/docs/migrate-avatars-to-local.md new file mode 100644 index 0000000..a4923eb --- /dev/null +++ b/docs/migrate-avatars-to-local.md @@ -0,0 +1,243 @@ +# 迁移方案:废弃云端 `mjk_avatars` 表,数字人元数据全量迁移到本地存储 + +> **状态:方案已调整(2026-04-17)** — 原方案中提到的 Celery 架构已完全移除,形象克隆现由 `app/scheduler/handlers/avatar_handler.py`(Async Engine Scheduler)统一调度。云端仍保留 `avatars` 表作为形象克隆的持久化记录。 + +## 方案目标 + +| 目标 | 说明 | +|------|------| +| ✅ 贯彻设计理念 | 真正做到**轻量云 + 全本地业务数据**,云端只记日志不存业务数据 | +| ✅ 统一接口日志 | 所有接口请求统一记录到 `mjk_interface_request_logs`,按接口统计积分消耗 | +| ✅ 简化后端代码 | 删除大量 CRUD、状态管理、定时任务代码,后端更干净 | +| ✅ 用户掌控数据 | 所有数字人元数据存在用户本地,云端只记克隆请求的消耗积分 | + +--- + +## 存储结构变化 + +### 变化前(现状) +``` +云端 PostgreSQL: mjk_avatars + └─ 存储所有数字人元数据 (name/voice_id/element_id/status 等) +前端本地: + └─ 只做缓存,从云端同步 +``` + +### 变化后(目标) +``` +云端 PostgreSQL: + ├─ mjk_interface_request_logs ← 只记:avatar_clone 请求 + 消耗积分 + 状态 + └─ mjk_avatars ← 废弃,不再写入新数据(存量可保留可删除) +用户本地磁盘: + └─ ~/Documents/Meijiaka/avatars/{avatar_id}/ + ├─ meta.json ← 完整数字人元数据(JSON) + └─ source.mp4 ← 原始上传视频 +``` + +--- + +## 本地存储结构定义 + +### 目录结构 +``` +~/Documents/Meijiaka/ +└── avatars/ + └── {avatar_id}/ # avatar_id = avt_{16位随机hex} + ├── meta.json # 元数据(JSON 格式) + └── source.mp4 # 原始上传视频 +``` + +### `meta.json` 结构 +```json +{ + "id": "avt_xxxxxxxxxxxxxxxx", + "name": "我的数字人", + "voiceId": "klingai-voice-id-string", + "elementId": 12345678, + "voiceTaskId": "kling-task-id-string", + "elementTaskId": "kling-task-id-string", + "videoUrl": "https://domain.com/path/to/source.mp4", + "trialUrl": "https://domain.com/path/to/trial.wav", + "status": "succeed", + "failReason": null, + "createdAt": "2026-04-16T10:00:00.000Z", + "updatedAt": "2026-04-16T10:05:00.000Z" +} +``` + +--- + +## 代码改动清单 + +### 后端 Python + +| 操作 | 文件 | 改动说明 | +|------|------|----------| +| 🆕 新增 | `app/models/interface_request_logs.py` | SQLAlchemy 模型 `InterfaceRequestLogs` | +| 🆕 新增 | `app/crud/interface_request_logs.py` | CRUD:create / update | +| ✏️ 修改 | `app/models/__init__.py` | 删除 `Avatar` 导入,新增 `InterfaceRequestLogs` | +| ✏️ 修改 | `app/api/v1/avatar.py` | 完全重写
• 保留:`POST /clone` / `GET /tasks/{id}` / `GET /clone/stream` / `POST /tasks/{id}/retry` / `DELETE /{id}`
• 删除:`GET /library` / `PATCH /{id}` / `/health` | +| ✏️ 修改 | `app/scheduler/handlers/avatar_handler.py` | 精简:删除所有对 `mjk_avatars` 读写,只记接口日志,进度放 Redis Registry | +| ❌ 删除 | `app/models/avatar.py` | 模型废弃,删除 | +| ❌ 删除 | `app/crud/avatar.py` | CRUD 废弃,删除 | +| ❌ 删除 | `app/tasks/avatar_clone.py` | 逻辑已合并到 avatar_handler,删除 | + +### Rust Tauri(`tauri-app/src-tauri/src/persistence.rs`) + +新增以下 IPC 命令: + +```rust +/// 列出所有本地数字人(按创建时间倒序) +#[tauri::command] +pub fn list_avatars(app: AppHandle) -> Result, String>; + +/// 保存数字人元数据 +#[tauri::command] +pub fn save_avatar(app: AppHandle, avatar_id: String, meta: AvatarMeta) -> Result<(), String>; + +/// 获取单个数字人元数据 +#[tauri::command] +pub fn get_avatar(app: AppHandle, avatar_id: String) -> Result, String>; + +/// 删除数字人(删除整个本地目录) +#[tauri::command] +pub fn delete_avatar(app: AppHandle, avatar_id: String) -> Result<(), String>; + +/// 更新数字人名称 +#[tauri::command] +pub fn update_avatar_name(app: AppHandle, avatar_id: String, name: String) -> Result<(), String>; +``` + +在 `lib.rs` 注册新命令。 + +### 前端 TypeScript + +| 模块 | 改动 | +|------|------| +| **Avatar 列表** | 原:`GET /avatar/library` 从后端获取 → 现在:调用 Tauri IPC 从本地读取 | +| **创建克隆** | 流程变化:
1. 前端生成 `avatar_id`
2. `POST /avatar/clone` → 获取 `task_id`
3. 前端创建本地目录 + 写入初始 `meta.json` (`status=pending`)
4. SSE 监听进度
5. 完成后 → 前端把 `voice_id`/`element_id` 写入本地 `meta.json`
6. 完成 | +| **删除 Avatar** | 流程变化:
1. 前端调用 `DELETE /avatar/{avatar_id}`(后端负责删除 Kling 远程资源)
2. 后端记删除日志到接口日志
3. 前端调用 IPC 删除本地目录 | +| **重命名 Avatar** | 原:调用后端 PATCH → 现在:前端直接修改本地 `meta.json`,无需请求后端 | +| **选择数字人生成视频** | 用法不变:从本地读取 `voice_id`/`element_id` → 传给后端视频生成接口 | + +--- + +## 工作流对比 + +### 改动前(当前) +``` +用户提交克隆 + → POST /clone → 后端写 mjk_avatars (status=pending) → 派发任务 + → Async Engine Scheduler (avatar_handler) 每一步都更新 `avatars` 表 + → 前端 SSE 轮询读 `avatars` 表拿进度 + → 完成后 Scheduler 更新 status=succeed 写入 voice_id/element_id + → 前端从 `avatars` 表读结果 → 缓存到本地 + → 列表从 `avatars` 表读取 +``` + +### 改动后(目标) +``` +用户提交克隆 + → 前端生成 avatar_id → 创建本地 meta.json (status=pending) + → POST /clone → 后端: + 1. 在 mjk_interface_request_logs 插入记录 + interface_type=avatar_clone, status=pending, started_at=now, cost_credits=X + 2. 注册到 Async Engine Scheduler (Redis Registry) + 3. 返回 {task_id, avatar_id} + → Async Engine Scheduler (avatar_handler) 执行: + 1. 调用 Kling 创建音色 → 轮询 → 获取 voice_id + 2. 调用 Kling 创建主体 → 轮询 → 获取 element_id + 3. 更新 Redis Registry 状态为 completed,写入结果 + 4. 更新接口日志: status=success/failed, finished_at=now + → 前端 SSE 从 TaskCache 获取结果 + → 完成后前端将 voice_id/element_id 写入本地 meta.json + → 列表展示直接从本地读取,不请求后端 +``` + +--- + +## 接口日志记录规则 + +`mjk_interface_request_logs` 对 `avatar_clone` 的记录: + +| 时机 | 操作 | 字段值 | +|------|------|--------| +| 刚收到请求 | 插入新记录 | `interface_type=avatar_clone`, `status=pending`, `started_at=NOW`, `cost_credits` = 克隆一次所需积分 | +| 任务完成成功 | 更新记录 | `status=success`, `finished_at=NOW` | +| 任务失败 | 更新记录 | `status=failed`, `finished_at=NOW`, `error_message=错误原因` | + +> 积分在请求创建时即扣除,因为无论成功失败,KlingAI 开始处理后会计费。 + +--- + +## 存量数据迁移策略 + +### 渐进迁移(对用户友好) + +1. **保留云端表**:`mjk_avatars` 保留不删除,存量数据继续存在 +2. **前端自动迁移**:用户首次打开形象库时: + - 前端检查:如果后端有数据但本地没有 → 提示用户"将云端数字人同步到本地" + - 用户确认后,前端逐个拉取数据写入本地 + - 同步完成后,后续只使用本地数据 +3. **下线旧表**:稳定运行一段时间后,可在维护窗口物理删除 `mjk_avatars` 表 + +### 回滚方案 +- 迁移过程中如果出问题,随时切回原逻辑(表保留,代码只需恢复删除部分) + +--- + +## 优缺点总结 + +| 优点 | 说明 | +|------|------| +| ✅ 完全符合需求 | 云端只存接口请求记录和消耗积分,不存用户业务数据 | +| ✅ 云端存储成本极低 | 只有接口日志,每条几KB,用户增长成本可控 | +| ✅ 后端代码大幅简化 | 删除了整个 Avatar CRUD、状态机管理、定时任务恢复,代码减少约 300 行 | +| ✅ 用户完全掌控数据 | 所有数字人元数据存储在用户本地磁盘 | +| ✅ 形象库展示更快 | 本地读取文件比查询数据库快很多 | +| ✅ 兼容存量数据 | 渐进迁移,可回滚 | + +| 缺点 | 说明 | 应对 | +|------|------|------| +| 用户换电脑需要迁移 | 用户需要自行迁移数据,或重新克隆 | 后续可增加导出/导入功能解决 | +| 本地硬盘损坏数据丢失 | 这是"全本地"设计的必然结果 | 符合项目初始"轻量云+全本地"设计理念,用户自担数据安全 | + +--- + +## 执行步骤(按顺序) + +1. **数据库** + - 生成 Alembic 迁移:所有表重命名加 `mjk_` 前缀 + 新建 `mjk_interface_request_logs` + - 修改所有 Python 模型中的 `__tablename__` + +2. **后端代码** + - 新建 `interface_request_logs` 模型和 CRUD + - 重写 `app/api/v1/avatar.py` + - 精简 `app/tasks/avatar_tasks.py` + - 删除废弃文件 + +3. **Rust Tauri** + - 在 `persistence.rs` 新增 avatar 相关 IPC 命令 + - 在 `lib.rs` 注册命令 + +4. **前端代码** + - 修改形象库:从本地读取 + - 修改创建流程:完成后写入本地 + - 修改删除流程:删除云端 Kling 资源后删除本地 + - 修改重命名:直接本地修改 + +5. **测试验证** + - 创建克隆 → 检查本地文件生成 → 检查接口日志写入 + - 列表展示 → 删除 → 重命名 全流程测试 + +--- + +## 相关文档 + +- [数据库设计规范](./database-design.md) - 完整的数据库命名规范和表结构 +- [视频生成流程](./video-generation-flow.md) - 完整视频生成流程说明 + +--- + +*版本:v1.0* +*创建日期:2026-04-16* diff --git a/docs/qiniu-kodo-python-sdk-guide.md b/docs/qiniu-kodo-python-sdk-guide.md new file mode 100644 index 0000000..bccfd23 --- /dev/null +++ b/docs/qiniu-kodo-python-sdk-guide.md @@ -0,0 +1,739 @@ +# 七牛云对象存储 (Kodo) Python SDK 开发规范 + +## 概述 + +本文档规范美家卡智影项目中使用七牛云对象存储 (Kodo) Python SDK 的开发标准,涵盖文件上传、下载、管理和 CDN 操作等核心功能。 + +**SDK 版本**: v5.0.0+ +**Python 版本**: 3.8+ (兼容 2.7 和 3.3+) +**官方文档**: https://developer.qiniu.com/kodo/1242/python + +--- + +## 1. 安装与初始化 + +### 1.1 安装 SDK + +```bash +pip install qiniu +``` + +### 1.2 初始化配置 + +```python +from qiniu import Auth + +# 从环境变量读取密钥(推荐) +import os +access_key = os.getenv('QINIU_ACCESS_KEY') +secret_key = os.getenv('QINIU_SECRET_KEY') + +# 构建鉴权对象 +q = Auth(access_key, secret_key) +``` + +**环境变量配置** (`.env` 文件): +```bash +QINIU_ACCESS_KEY=your-access-key +QINIU_SECRET_KEY=your-secret-key +QINIU_BUCKET_NAME=your-bucket-name +QINIU_BUCKET_DOMAIN=your-domain.com +``` + +--- + +## 2. 文件上传 + +### 2.1 上传方式选择 + +| 场景 | 推荐方式 | 说明 | +|------|----------|------| +| 小文件 (< 100MB) | 表单上传 (put_file) | 简单快速,一次请求完成 | +| 大文件 (> 100MB) | 分片上传 v2 (put_file_v2) | 支持断点续传,适应弱网环境 | +| 网络不稳定 | 分片上传 v2 | 自动重试,更可靠 | + +### 2.2 服务端生成上传 Token + +```python +from qiniu import Auth + +def generate_upload_token( + bucket_name: str, + key: str = None, + expires: int = 3600, + policy: dict = None +) -> str: + """ + 生成上传凭证 + + Args: + bucket_name: 存储空间名称 + key: 指定文件名(可选) + expires: Token 有效期(秒),默认 3600 + policy: 上传策略配置(可选) + + Returns: + 上传 Token 字符串 + """ + q = Auth(access_key, secret_key) + + # 自定义上传策略(可选) + if policy is None: + policy = {} + + token = q.upload_token(bucket_name, key, expires, policy) + return token +``` + +### 2.3 客户端直传(推荐) + +**服务端生成 Token,客户端直传到七牛云**: + +```python +# 服务端 API +from fastapi import APIRouter +from pydantic import BaseModel + +router = APIRouter(prefix="/qiniu", tags=["Qiniu"]) + +class UploadTokenRequest(BaseModel): + key: str # 文件名 + expires: int = 3600 # Token 有效期 + +class UploadTokenResponse(BaseModel): + token: str + key: str + upload_url: str = "https://upload.qiniup.com" + +@router.post("/upload-token", response_model=UploadTokenResponse) +async def get_upload_token(request: UploadTokenRequest): + """获取上传凭证,客户端直传""" + token = generate_upload_token( + bucket_name=os.getenv('QINIU_BUCKET_NAME'), + key=request.key, + expires=request.expires + ) + return UploadTokenResponse(token=token, key=request.key) +``` + +### 2.4 服务端上传文件(保留场景) + +```python +from qiniu import Auth, put_file_v2, etag +import qiniu.config + +def upload_file( + local_file_path: str, + key: str, + bucket_name: str = None +) -> dict: + """ + 服务端上传文件到七牛云 + + Args: + local_file_path: 本地文件路径 + key: 存储的文件名(如 "audios/voice.mp3") + bucket_name: 存储空间名称 + + Returns: + {"key": str, "hash": str, "url": str} + """ + bucket_name = bucket_name or os.getenv('QINIU_BUCKET_NAME') + + # 生成上传 Token + token = q.upload_token(bucket_name, key, 3600) + + # 使用分片上传 v2(推荐) + ret, info = put_file_v2( + up_token=token, + key=key, + file_path=local_file_path, + version='v2' # 指定分片上传 v2 版本 + ) + + if ret is None: + raise Exception(f"上传失败: {info}") + + # 验证文件完整性 + assert ret['key'] == key + assert ret['hash'] == etag(local_file_path) + + # 构建访问 URL + domain = os.getenv('QINIU_BUCKET_DOMAIN') + url = f"https://{domain}/{key}" + + return { + "key": ret['key'], + "hash": ret['hash'], + "url": url + } +``` + +### 2.5 上传策略 (PutPolicy) + +常用策略配置: + +```python +# 1. 限制文件大小 (10MB ~ 100MB) +policy = { + "fsizeMin": 1024 * 1024 * 10, # 最小 10MB + "fsizeLimit": 1024 * 1024 * 100, # 最大 100MB + "mimeLimit": "audio/*;video/*" # 限制文件类型 +} + +# 2. 上传后回调业务服务器 +policy = { + "callbackUrl": "https://your-api.com/callback", + "callbackBody": "key=$(key)&hash=$(etag)&fname=$(fname)&fsize=$(fsize)", + "callbackBodyType": "application/x-www-form-urlencoded" +} + +# 3. 上传后转码(持久化处理) +import base64 +fops = 'avthumb/mp4/s/640x360/vb/1.25m' +saveas_key = base64.urlsafe_b64encode(f'{bucket_name}:output.mp4'.encode()).decode() + +policy = { + "persistentOps": f"{fops}|saveas/{saveas_key}", + "persistentPipeline": "transcoding", # 队列名称 + "persistentNotifyUrl": "https://your-api.com/pfop/callback" +} +``` + +--- + +## 3. 文件下载 + +### 3.1 公有空间下载 + +公有空间文件可直接访问: + +```python +def get_public_url(key: str, domain: str = None) -> str: + """获取公有空间文件 URL""" + domain = domain or os.getenv('QINIU_BUCKET_DOMAIN') + return f"https://{domain}/{key}" +``` + +### 3.2 私有空间下载(临时 URL) + +```python +import requests + +def get_private_url(key: str, expires: int = 3600) -> str: + """ + 生成私有空间文件的临时下载 URL + + Args: + key: 文件 Key + expires: 链接有效期(秒) + + Returns: + 带签名的临时 URL + """ + domain = os.getenv('QINIU_BUCKET_DOMAIN') + base_url = f"https://{domain}/{key}" + + # 生成私有下载链接 + private_url = q.private_download_url(base_url, expires=expires) + return private_url + +# 使用示例 +def download_file(key: str, local_path: str): + """下载私有空间文件到本地""" + private_url = get_private_url(key, expires=3600) + + response = requests.get(private_url) + if response.status_code == 200: + with open(local_path, 'wb') as f: + f.write(response.content) + return True + return False +``` + +--- + +## 4. 文件管理 (BucketManager) + +### 4.1 初始化管理器 + +```python +from qiniu import Auth, BucketManager + +q = Auth(access_key, secret_key) +bucket = BucketManager(q) +``` + +### 4.2 获取文件信息 + +```python +def get_file_info(bucket_name: str, key: str) -> dict: + """ + 获取文件元信息 + + Returns: + { + "fsize": 文件大小(字节), + "hash": 文件哈希, + "mimeType": MIME类型, + "putTime": 上传时间(100纳秒时间戳), + "type": 存储类型(0=标准,1=低频,2=归档,3=深度归档) + } + """ + ret, info = bucket.stat(bucket_name, key) + if ret is None: + raise Exception(f"获取文件信息失败: {info}") + return ret +``` + +### 4.3 列举文件列表 + +```python +from typing import List, Optional + +def list_files( + bucket_name: str, + prefix: str = None, # 前缀筛选 + limit: int = 100, # 每页数量 + marker: str = None # 分页标记 +) -> dict: + """ + 列举空间文件列表 + + Returns: + { + "items": [{"key": ..., "fsize": ..., ...}], + "marker": "分页标记", + "commonPrefixes": ["公共前缀列表"] + } + """ + ret, eof, info = bucket.list( + bucket_name, + prefix=prefix, + marker=marker, + limit=limit, + delimiter=None # 不指定分隔符 + ) + + return { + "items": ret.get('items', []), + "marker": ret.get('marker'), + "eof": eof # 是否已列举完 + } + +# 遍历所有文件 +def list_all_files(bucket_name: str, prefix: str = None) -> List[dict]: + """遍历获取所有文件""" + files = [] + marker = None + + while True: + result = list_files(bucket_name, prefix, limit=1000, marker=marker) + files.extend(result['items']) + + if result['eof'] or not result['marker']: + break + marker = result['marker'] + + return files +``` + +### 4.4 删除文件 + +```python +def delete_file(bucket_name: str, key: str) -> bool: + """删除单个文件""" + ret, info = bucket.delete(bucket_name, key) + return ret == {} + +def delete_files_batch(bucket_name: str, keys: List[str]) -> dict: + """批量删除文件""" + from qiniu import build_batch_delete + + ops = build_batch_delete(bucket_name, keys) + ret, info = bucket.batch(ops) + return ret +``` + +### 4.5 复制和移动文件 + +```python +def copy_file( + src_bucket: str, + src_key: str, + dest_bucket: str, + dest_key: str, + force: bool = True +) -> bool: + """复制文件""" + ret, info = bucket.copy( + src_bucket, src_key, + dest_bucket, dest_key, + force=force # 强制覆盖 + ) + return ret is not None + +def move_file( + src_bucket: str, + src_key: str, + dest_bucket: str, + dest_key: str, + force: bool = True +) -> bool: + """移动/重命名文件""" + ret, info = bucket.move( + src_bucket, src_key, + dest_bucket, dest_key, + force=force + ) + return ret is not None +``` + +### 4.6 修改文件元信息 + +```python +def change_mime(bucket_name: str, key: str, mime_type: str): + """修改文件 MIME 类型""" + ret, info = bucket.change_mime(bucket_name, key, mime_type) + return ret is not None + +def change_type(bucket_name: str, key: str, file_type: int): + """ + 修改文件存储类型 + + file_type: + 0 = 标准存储 + 1 = 低频存储 + 2 = 归档存储 + 3 = 深度归档存储 + """ + ret, info = bucket.change_type(bucket_name, key, file_type) + return ret is not None +``` + +### 4.7 批量操作 + +```python +from qiniu import ( + build_batch_stat, + build_batch_copy, + build_batch_move, + build_batch_rename, + build_batch_delete +) + +def batch_stat(bucket_name: str, keys: List[str]) -> List[dict]: + """批量查询文件信息""" + ops = build_batch_stat(bucket_name, keys) + ret, info = bucket.batch(ops) + return ret + +def batch_rename( + bucket_name: str, + key_map: dict, # {"old_key": "new_key", ...} + force: bool = True +): + """批量重命名""" + ops = build_batch_rename(bucket_name, key_map, force=force) + ret, info = bucket.batch(ops) + return ret + +def batch_copy( + src_bucket: str, + key_map: dict, # {"src_key": "dest_key", ...} + dest_bucket: str = None, + force: bool = True +): + """批量复制""" + dest_bucket = dest_bucket or src_bucket + ops = build_batch_copy(src_bucket, key_map, dest_bucket, force=force) + ret, info = bucket.batch(ops) + return ret +``` + +### 4.8 抓取网络资源 + +```python +def fetch_remote_file( + remote_url: str, + key: str, + bucket_name: str = None +) -> dict: + """ + 抓取远程文件到七牛云 + + Args: + remote_url: 远程文件 URL + key: 保存的文件名 + bucket_name: 目标空间 + + Returns: + {"key": ..., "hash": ..., "fsize": ...} + """ + bucket_name = bucket_name or os.getenv('QINIU_BUCKET_NAME') + ret, info = bucket.fetch(remote_url, bucket_name, key) + return ret +``` + +--- + +## 5. CDN 操作 + +### 5.1 初始化 CDN Manager + +```python +from qiniu import CdnManager + +cdn_manager = CdnManager(q) +``` + +### 5.2 刷新 CDN 缓存 + +```python +def refresh_urls(urls: List[str]) -> dict: + """刷新指定 URL 的 CDN 缓存""" + ret, info = cdn_manager.refresh_urls(urls) + return ret + +def refresh_dirs(dirs: List[str]) -> dict: + """刷新整个目录的 CDN 缓存""" + ret, info = cdn_manager.refresh_dirs(dirs) + return ret +``` + +### 5.3 预取资源 + +```python +def prefetch_urls(urls: List[str]) -> dict: + """预取资源到 CDN 节点""" + ret, info = cdn_manager.prefetch_urls(urls) + return ret +``` + +### 5.4 获取 CDN 日志 + +```python +def get_cdn_log_list(domains: List[str], log_date: str) -> List[dict]: + """ + 获取 CDN 日志下载链接 + + Args: + domains: 域名列表 + log_date: 日期 (YYYY-MM-DD) + + Returns: + [{"name": ..., "url": ..., "size": ..., "mtime": ...}] + """ + ret, info = cdn_manager.get_log_list_data(domains, log_date) + return ret.get('data', []) +``` + +--- + +## 6. 项目集成方案 + +### 6.1 服务端封装模块 + +```python +# app/services/qiniu_service.py +""" +七牛云对象存储服务封装 +""" + +import os +from typing import List, Optional +from qiniu import Auth, BucketManager, CdnManager, put_file_v2, etag + +class QiniuService: + """七牛云服务封装""" + + def __init__(self): + access_key = os.getenv('QINIU_ACCESS_KEY') + secret_key = os.getenv('QINIU_SECRET_KEY') + self.bucket_name = os.getenv('QINIU_BUCKET_NAME') + self.domain = os.getenv('QINIU_BUCKET_DOMAIN') + + self.auth = Auth(access_key, secret_key) + self.bucket = BucketManager(self.auth) + self.cdn = CdnManager(self.auth) + + def get_upload_token(self, key: str, expires: int = 3600, policy: dict = None) -> str: + """生成上传 Token""" + return self.auth.upload_token(self.bucket_name, key, expires, policy) + + def get_file_url(self, key: str, private: bool = False, expires: int = 3600) -> str: + """获取文件访问 URL""" + base_url = f"https://{self.domain}/{key}" + if private: + return self.auth.private_download_url(base_url, expires) + return base_url + + def upload_file(self, local_path: str, key: str) -> dict: + """服务端上传文件""" + token = self.get_upload_token(key) + ret, info = put_file_v2(token, key, local_path, version='v2') + + if ret is None: + raise Exception(f"上传失败: {info}") + + return { + "key": ret['key'], + "hash": ret['hash'], + "url": self.get_file_url(key) + } + + def delete_file(self, key: str) -> bool: + """删除文件""" + ret, info = self.bucket.delete(self.bucket_name, key) + return ret == {} + + def refresh_cdn(self, keys: List[str]) -> dict: + """刷新 CDN 缓存""" + urls = [self.get_file_url(key) for key in keys] + return self.cdn.refresh_urls(urls) + +# 全局单例 +_qiniu_service: Optional[QiniuService] = None + +def get_qiniu_service() -> QiniuService: + global _qiniu_service + if _qiniu_service is None: + _qiniu_service = QiniuService() + return _qiniu_service +``` + +### 6.2 FastAPI 路由集成 + +```python +# app/api/v1/qiniu.py + +from fastapi import APIRouter, UploadFile, File +from app.services.qiniu_service import get_qiniu_service + +router = APIRouter(prefix="/qiniu", tags=["Qiniu"]) + +@router.post("/upload-token") +async def get_upload_token(key: str, expires: int = 3600): + """获取客户端直传 Token""" + service = get_qiniu_service() + token = service.get_upload_token(key, expires) + return {"token": token, "key": key} + +@router.post("/upload") +async def upload_file(file: UploadFile = File(...), key: str = None): + """服务端上传文件(小文件场景)""" + import tempfile + import shutil + + service = get_qiniu_service() + + # 生成唯一文件名 + if key is None: + import uuid + ext = file.filename.split('.')[-1] if '.' in file.filename else '' + key = f"uploads/{uuid.uuid4()}.{ext}" if ext else f"uploads/{uuid.uuid4()}" + + # 保存临时文件 + with tempfile.NamedTemporaryFile(delete=False) as tmp: + shutil.copyfileobj(file.file, tmp) + tmp_path = tmp.name + + try: + result = service.upload_file(tmp_path, key) + return result + finally: + os.unlink(tmp_path) + +@router.delete("/files/{key:path}") +async def delete_file(key: str): + """删除文件""" + service = get_qiniu_service() + success = service.delete_file(key) + return {"success": success} +``` + +--- + +## 7. 最佳实践 + +### 7.1 文件名规范 + +```python +def generate_key(file_type: str, user_id: str, filename: str) -> str: + """ + 生成规范的文件存储路径 + + 格式: {type}/{user_id}/{date}/{uuid}.{ext} + """ + import uuid + from datetime import datetime + + ext = filename.split('.')[-1] if '.' in filename else 'bin' + date = datetime.now().strftime('%Y%m') + unique_id = str(uuid.uuid4())[:8] + + return f"{file_type}/{user_id}/{date}/{unique_id}.{ext}" + +# 使用示例 +key = generate_key("voices", "user_123", "my-voice.mp3") +# 结果: voices/user_123/202501/a1b2c3d4.mp3 +``` + +### 7.2 错误处理 + +```python +from qiniu import AuthError, HTTPError + +def handle_qiniu_error(func): + """七牛云操作错误处理装饰器""" + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except AuthError as e: + raise Exception(f"认证失败: {e}") + except HTTPError as e: + raise Exception(f"请求失败: {e}") + except Exception as e: + raise Exception(f"操作失败: {e}") + return wrapper +``` + +### 7.3 安全配置 + +1. **密钥管理**: 使用环境变量,禁止硬编码 +2. **Token 有效期**: 上传 Token 建议 1 小时,下载 Token 根据场景设置 +3. **上传策略**: 限制文件大小和 MIME 类型 +4. **私有空间**: 敏感文件使用私有空间 + 临时 URL + +--- + +## 8. 常见问题 + +### Q1: 上传失败,返回 401 错误? + +**A**: 检查 AccessKey 和 SecretKey 是否正确,以及 Token 是否过期。 + +### Q2: 如何支持大文件上传? + +**A**: 使用分片上传 v2 (`put_file_v2`),SDK 会自动处理分片和断点续传。 + +### Q3: 文件上传后如何获取访问 URL? + +**A**: 公有空间直接拼接 `https://{domain}/{key}`,私有空间使用 `auth.private_download_url()` 生成临时 URL。 + +### Q4: 如何刷新 CDN 缓存? + +**A**: 使用 `CdnManager.refresh_urls()` 或 `refresh_dirs()`,注意目录刷新有每日限额。 + +### Q5: 上传回调不生效? + +**A**: 确保 callbackUrl 是公网可访问的 HTTPS 地址,且返回 Content-Type: application/json。 + +--- + +## 9. 参考资料 + +- [七牛云 Python SDK 官方文档](https://developer.qiniu.com/kodo/1242/python) +- [上传策略文档](https://developer.qiniu.com/kodo/1206/put-policy) +- [表单上传 API](https://developer.qiniu.com/kodo/1272/api-overview) +- [Python SDK GitHub](https://github.com/qiniu/python-sdk) diff --git a/docs/semantic-refactoring-plan.md b/docs/semantic-refactoring-plan.md new file mode 100644 index 0000000..d9846d2 --- /dev/null +++ b/docs/semantic-refactoring-plan.md @@ -0,0 +1,425 @@ +# 后端语义治理与架构重构计划 + +> **范围**:`python-api/app/` 全目录 +> **目标**:根治需求调整与 Celery→Async Scheduler 迁移导致的命名腐败、语义漂移、类型漂移问题 +> **原则**:长期稳定优先,不计短期开发成本 +> **状态**:计划中(待团队评审后执行) + +--- + +## 一、问题诊断 + +经过深度代码扫描,当前代码存在六大类语义腐败,按严重程度排序: + +### P0:调度器核心模型的 `params: dict[str, Any]` 类型漂移 +- **症状**:所有 Handler 从裸字典里 `params.get("shots")`,且因 Redis 序列化反复写 `if isinstance(shots, str): shots = json.loads(shots)` +- **风险**:这是 Scheduler 取代 Celery 后最脆弱的环节,任何字段名改动都会导致运行时崩溃 +- **关键文件**:`app/scheduler/models.py`, `app/scheduler/registry.py`, `app/scheduler/handlers/video_handler.py` + +### P0:`shot/segment/scene` 的三重命名 + 四处重复定义 +- **症状**:`ScriptShot`(Schema)、`ShotData`(API)、`ShotTask`(Service)、`ShotUnit`(Scheduler)四者并存,字段名 `scene`/`scene_desc`、`type`/`shot_type` 混用 +- **风险**:任何分镜字段改动必须改 4 个类,极易遗漏 +- **关键文件**:`app/schemas/script.py`, `app/api/v1/video.py`, `app/services/kling_video_service.py`, `app/scheduler/models.py` + +### P0:Kling 供应商语义大规模泄漏到业务层和 API 层 +- **症状**:`element_id`(Kling 主体 ID)、`kling_task_id`、Omni prompt 语法 `<<>>` 直接出现在 API Schema、Scheduler 模型、数据库模型中 +- **风险**:一旦更换视频供应商,影响面会穿透所有层级 +- **关键文件**:`app/api/v1/video.py`, `app/api/v1/tasks.py`, `app/models/avatar.py`, `app/scheduler/handlers/video_handler.py` + +### P1:`task` / `task_id` 的五重语义混用 +- **症状**:FastAPI BackgroundTask、Scheduler Task、Kling API Task、AnyToCopy Task、Volcengine Task 都叫 `task` +- **风险**:日志堆栈中无法区分层级,调试极其困难 +- **关键文件**:`app/api/v1/tasks.py`, `app/scheduler/`, `app/ai/providers/klingai_provider.py`, `app/services/` + +### P1:历史残留命名与双轨运行 +- **症状**:`# 兼容旧字段`、`video_task_id`、`image_task_id`、`cache_err` 等 Celery 时代残留;`script` 任务仍走 BackgroundTask,其他任务走 Scheduler +- **风险**:双轨运行导致统一监控、重试、日志无法覆盖全部任务类型 +- **关键文件**:`app/scheduler/handlers/video_handler.py`, `app/scheduler/handlers/image_handler.py`, `app/api/v1/tasks.py` + +### P2:CRUD 层裸字典更新 +- **症状**:`avatar_crud.update(db, db_obj=avatar, obj_in={"status": "element_pending"})` +- **风险**:拼写错误、状态值非法无法被静态检查捕获 +- **关键文件**:`app/crud/base.py`, `app/crud/avatar.py`, `app/scheduler/handlers/avatar_handler.py` + +--- + +## 二、架构目标:六层语义治理 + +我们将整个后端严格划分为 **6 个语义层级**,每一层只使用属于该层的术语: + +``` +┌─────────────────────────────────────────────────────────┐ +│ Layer 6: Presentation (API Schema / 前端适配层) │ +│ 术语: Segment, Human, Job, Script, Cover │ +├─────────────────────────────────────────────────────────┤ +│ Layer 5: Application (API 路由 / BackgroundJob) │ +│ 术语: Segment, Human, Job, Project │ +├─────────────────────────────────────────────────────────┤ +│ Layer 4: Orchestration (Scheduler / SlotManager) │ +│ 术语: Job, JobRecord, Slot, Handler │ +├─────────────────────────────────────────────────────────┤ +│ Layer 3: Domain (Service / 业务逻辑) │ +│ 术语: Segment, Human, VideoComposition, Caption │ +├─────────────────────────────────────────────────────────┤ +│ Layer 2: Adapter (Provider Client / 供应商适配) │ +│ 术语: KlingJob, KlingElement, VolcJob, ProviderTaskId │ +├─────────────────────────────────────────────────────────┤ +│ Layer 1: Infrastructure (DB / Redis / HTTP / FileSys) │ +│ 术语: 仅使用底层技术术语 │ +└─────────────────────────────────────────────────────────┘ +``` + +### 核心禁令 + +1. `element`、`omni`、`kling_task_id` 等**供应商术语**禁止出现在 Layer 3 以上 +2. `shot` 禁止出现在 Layer 3 以上(Kling 术语,业务层统一叫 `segment`) +3. `task` 禁止出现在 Layer 4(Scheduler 内部统一叫 `job`) +4. `dict[str, Any]` 禁止出现在跨层传递的接口中 + +--- + +## 三、重构阶段(Phase 1-5) + +每个 Phase 独立成组,建议按顺序执行。每个 Phase 完成后必须跑通 `pytest` 和 `make lint`。 + +--- + +### Phase 1:Schema 统一 + 状态机 Enum 化 +**目标**:建立"单一真相源",消除 shot/segment/scene 的四重定义 +**预估工时**:3-4 天 +**影响面**:全项目基础类型 + +#### Task 1.1:新建统一术语 Schema +- [ ] 新建 `app/schemas/segment.py` + ```python + class Segment(BaseModel): + id: str + type: Literal["segment", "empty_shot"] + scene: str # 统一为 scene,删除 scene_desc + voiceover: str + duration: int | None = None + human_id: str | None = None # 业务术语,对应 Kling 的 element_id + status: SegmentStatus = SegmentStatus.PENDING + provider_task_id: str | None = None + video_url: str | None = None + local_path: str | None = None + query_fail_count: int = 0 + ``` +- [ ] 新建 `app/schemas/enums.py`,定义以下 Enum: + - `JobStatus`: pending, running, completed, failed + - `SegmentStatus`: pending, submitted, completed, failed + - `AvatarCloneStatus`: pending, voice_processing, voice_failed, element_pending, element_processing, element_failed, succeed + - `KlingTaskStatus`: submitted, processing, succeed, failed(仅限 Provider 层使用) +- [ ] 新建 `app/schemas/job.py`,定义 `JobParams` Union: + - `VideoJobParams`(含 `segments: list[Segment]`) + - `ImageJobParams` + - `SubtitleJobParams` + - `CopyJobParams` + - `AvatarCloneJobParams` + +#### Task 1.2:删除重复定义 +- [ ] 删除 `app/scheduler/models.py` 中的 `ShotUnit` +- [ ] 删除/重构 `app/services/kling_video_service.py` 中的 `ShotTask`(字段迁移到 `Segment`) +- [ ] 删除 `app/api/v1/video.py` 中的 `ShotData`,改为引用 `Segment` +- [ ] 将 `app/schemas/script.py` 中的 `ScriptShot` 改为 `Segment` 的别名或类型适配器 + +#### Task 1.3:字段名统一 +- [ ] 批量将 `scene_desc` 重命名为 `scene`(覆盖 `kling_video_service.py`, `video_handler.py` 等) +- [ ] 批量将 `shot_type` 重命名为 `type`(在业务层/Schema 层;Provider 层保留 `shot_type` 仅用于 Kling API 调用) +- [ ] `app/api/v1/tasks.py` 中的 `shots: list[dict]` 改为 `segments: list[Segment]` + +#### Task 1.4:状态字符串 Enum 化 +- [ ] `app/scheduler/models.py` 中 `TaskRecord.status` 类型改为 `JobStatus` +- [ ] `app/services/kling_video_service.py` 中 `VideoGenerationJob.status` 类型改为 `JobStatus` +- [ ] `app/models/avatar.py` 中 `Avatar.status` 类型改为 `AvatarCloneStatus` +- [ ] `app/ai/providers/klingai_provider.py` 中所有 Kling 状态字符串操作改为 `KlingTaskStatus` + +#### 验收标准 +- [ ] `grep -rn "class ShotUnit\|class ShotTask\|class ShotData\|class ScriptShot" app/` 返回为空(除了注释或别名声明) +- [ ] `grep -rn "scene_desc" app/ | grep -v "__pycache__"` 返回为空 +- [ ] `mypy app/schemas/` 无类型错误 +- [ ] `pytest` 通过 + +--- + +### Phase 2:Scheduler 层全面"去 task 化" +**目标**:消除 `task` 的五重语义混用,建立 `Job` 专属语义域 +**预估工时**:3-4 天 +**影响面**:`app/scheduler/` 目录及引用方 + +#### Task 2.1:核心模型与 Registry 重命名 +- [ ] `app/scheduler/models.py`:`TaskRecord` → `JobRecord` +- [ ] `app/scheduler/registry.py`:`TaskRegistry` → `JobRegistry` + - 所有 `task_id` 参数/字段 → `job_id` + - 所有 `task_type` 参数/字段 → `job_type` + - `running_task_ids` → `running_job_ids` +- [ ] `app/scheduler/engine.py`:`AsyncEngine` 中所有 `task` → `job` + +#### Task 2.2:Registry 承担全部序列化职责 +- [ ] 在 `JobRegistry.get()` 中统一完成 JSON 反序列化 + - 解析 `params` 字段时,根据 `job_type` 路由到正确的 `JobParams` Pydantic 模型 + - 保证 Handler 拿到的 `job.params` 永远是强类型对象 +- [ ] 删除 `video_handler.py` 和 `image_handler.py` 中所有的 `isinstance(shots, str): json.loads` 逻辑 +- [ ] 删除 `_download_and_upload` 中的手动 JSON 类型判断 + +#### Task 2.3:`StateChange` 取代裸字典 +- [ ] 在 `app/scheduler/models.py` 中定义: + ```python + @dataclass(frozen=True) + class StateChange: + job_id: str + field: str + value: Any + ``` +- [ ] 修改 `app/scheduler/engine.py`: + - `_apply_changes(self, changes: list[dict[str, Any]])` → `_apply_changes(self, changes: list[StateChange])` + - 序列化逻辑移入 `StateChange.to_redis_command()` 方法 +- [ ] 修改 `app/scheduler/handlers/base.py`:`tick()` 返回类型改为 `list[StateChange]` +- [ ] 修改所有 Handler:`changes.append({"task_id": ..., "field": ...})` → `changes.append(StateChange(job_id=..., field=..., value=...))` + +#### Task 2.4:API 层适配 +- [ ] `app/api/v1/tasks.py` 中:内部变量名 `task_id` 在引用 Scheduler 时改为 `job_id`(API URL `/tasks/{task_id}` 保持不变,仅内部变量和注释调整) +- [ ] `app/api/v1/avatar.py` 中:引用 `TaskRegistry` 的地方改为 `JobRegistry` + +#### 验收标准 +- [ ] `grep -rn "TaskRecord\|TaskRegistry\|running_task_ids" app/scheduler/` 返回为空 +- [ ] `grep -rn "isinstance(.*shots.*str)" app/scheduler/handlers/` 返回为空 +- [ ] `grep -rn '"task_id":' app/scheduler/handlers/` 返回为空(仅 `StateChange` dataclass 内部可保留) +- [ ] `pytest` 通过 + +--- + +### Phase 3:建立"供应商防火墙"(Adapter 层隔离) +**目标**:将 Kling/Volc 术语彻底下压到 Provider 层,业务层以上使用纯业务语义 +**预估工时**:4-5 天 +**影响面**:API Schema、DB 模型、Scheduler 模型、Provider 层 + +#### Task 3.1:API 层删除 Kling 术语泄漏 +- [ ] `app/api/v1/video.py`: + - `element_id: int | None = Field(None, description="Kling主体ID")` → `human_id: int | None = Field(None, description="数字人主体ID")` +- [ ] `app/api/v1/tasks.py`: + - 同上,所有 `element_id` → `human_id` + - `VideoParams` 中的 `shots` → `segments` + +#### Task 3.2:DB 模型增加供应商抽象 +- [ ] `app/models/avatar.py`: + - `element_id: Mapped[int | None]` → `provider_element_id: Mapped[int | None]` + - `voice_task_id` → `provider_voice_job_id` + - `element_task_id` → `provider_element_job_id` + - 新增 `provider: Mapped[str] = mapped_column(default="kling")`(为未来多供应商做准备) +- [ ] 生成 Alembic 迁移脚本(字段重命名 + 新增字段) + +#### Task 3.3:Scheduler 模型供应商抽象 +- [ ] `app/scheduler/models.py`(Phase 2 后的 `JobRecord` 及 `Segment`): + - `kling_task_id` → `provider_task_id` + - 如需追溯供应商,增加 `provider: str = "kling"` + +#### Task 3.4:Provider 返回值模型化 +- [ ] 新建 `app/ai/providers/kling_dto.py`: + - `KlingVideoResult` + - `KlingImageResult` + - `KlingVoiceResult` + - `KlingElementResult` +- [ ] 修改 `app/ai/providers/klingai_provider.py`: + - 所有返回裸 `dict[str, Any]` 的方法改为返回对应的 `Kling*Result` + - 状态字段类型改为 `KlingTaskStatus` + +#### Task 3.5:Prompt 语法迁移到 Provider 层 +- [ ] 删除 `app/scheduler/handlers/video_handler.py` 中的: + - `_build_omni_prompt()` + - `_build_empty_shot_video_prompt()` +- [ ] 在 `app/ai/providers/klingai_provider.py` 中新建 `KlingPromptBuilder` 类: + ```python + class KlingPromptBuilder: + @staticmethod + def omni_segment(scene: str, voiceover: str, element_id: str | None = None) -> str + @staticmethod + def empty_shot(scene: str, voiceover: str) -> str + ``` +- [ ] `video_handler.py` 调用时只传业务字段(`scene`, `voiceover`, `human_id`),由 Provider 层负责映射为 Kling 语法 + +#### Task 3.6:Service 层适配器映射 +- [ ] `app/services/kling_video_service.py`: + - 删除 `avatar_id` 废弃字段 + - `human_id` → 在调用 Provider 时映射为 `element_id` +- [ ] `app/services/qiniu_service.py`: + - `file_type="avatar"` → `file_type="clone_video"` 或 `"human_video"` + +#### 验收标准 +- [ ] `grep -rn "element_id" app/api/ app/schemas/ app/scheduler/models.py | grep -v "provider_element_id"` 返回为空 +- [ ] `grep -rn "kling_task_id" app/api/ app/schemas/ app/scheduler/models.py` 返回为空 +- [ ] `grep -rn "<<>>\|<<>>" app/scheduler/ app/services/` 返回为空(仅在 Provider 层保留) +- [ ] Alembic 迁移脚本可正常升级/降级 +- [ ] `pytest` 通过 + +--- + +### Phase 4:清理历史残留 + 消除双轨运行 +**目标**:删除所有 Celery 时代残留,将 `script` 任务纳入 Scheduler 统一调度 +**预估工时**:2-3 天 +**影响面**:Handler、API 路由、历史命名 + +#### Task 4.1:删除兼容旧字段代码 +- [ ] `app/scheduler/handlers/video_handler.py`: + - 删除 `shot["video_task_id"] = kling_task_id # 兼容旧字段` + - 删除初始化 shots 时的 `"video_task_id": None` +- [ ] `app/scheduler/handlers/image_handler.py`: + - 删除 `params["image_task_id"] = kling_task_id` +- [ ] `app/services/kling_video_service.py`: + - 删除 `avatar_id` 字段 + +#### Task 4.2:修正历史残留命名 +- [ ] `app/core/redis_client.py`:删除文档字符串中的 `RateLimiter` 字样 +- [ ] `app/api/v1/tasks.py`: + - `cache entry` → `registry entry` + - `cache_err` → `registry_err` + - `Failed to update cache` → `Failed to update registry` +- [ ] `app/core/token_manager.py`:`_background_tasks` → `_refresh_tasks` +- [ ] 删除 `app/services/dto.py` + +#### Task 4.3:将 `script` 任务迁移到 Scheduler +- [ ] 新建 `app/scheduler/handlers/script_handler.py` + - 将 `app/api/v1/tasks.py` 中 `_run_script_task` 的逻辑迁移至此 + - 继承 `AsyncHandler`,`name = "script"`,不占用 Slot(或占用独立 `script_slots`) +- [ ] 修改 `app/api/v1/tasks.py`: + - `script` 任务改为 `registry.create(job_type="script", ...)` + - 删除 `BackgroundTasks` 相关参数和 `_run_script_task` 函数 +- [ ] 修改 `app/scheduler/main.py`:注册 `ScriptHandler` + +#### 验收标准 +- [ ] `grep -rn "兼容旧字段\|video_task_id\|image_task_id" app/scheduler/ app/services/` 返回为空 +- [ ] `grep -rn "cache_err\|cache entry" app/api/v1/tasks.py` 返回为空 +- [ ] `app/services/dto.py` 不存在 +- [ ] `app/api/v1/tasks.py` 中无 `BackgroundTasks` 导入和使用 +- [ ] `pytest` 通过 + +--- + +### Phase 5:CRUD 层强类型化 +**目标**:消灭 CRUD 层的裸字典更新 +**预估工时**:2 天 +**影响面**:CRUD Base、Avatar CRUD、Scheduler Handler + +#### Task 5.1:CRUD Base 类型约束 +- [ ] `app/crud/base.py`: + - `obj_in: dict[str, Any]` → `obj_in: CreateSchemaType | UpdateSchemaType` + - 保留 `dict` 仅作为 `update` 的 fallback,但所有业务调用方优先使用 Schema + +#### Task 5.2:Avatar Schema 定义 +- [ ] 新建 `app/schemas/avatar.py`: + ```python + class AvatarCreate(BaseModel): + name: str + video_url: str + status: AvatarCloneStatus = AvatarCloneStatus.PENDING + + class AvatarUpdate(BaseModel): + name: str | None = None + status: AvatarCloneStatus | None = None + provider_voice_job_id: str | None = None + provider_element_job_id: str | None = None + provider_element_id: int | None = None + fail_reason: str | None = None + ``` +- [ ] `app/crud/avatar.py`:改为 `class CRUDAvatar(CRUDBase[Avatar, AvatarCreate, AvatarUpdate])` + +#### Task 5.3:Handler 调用方改造 +- [ ] `app/scheduler/handlers/avatar_handler.py`: + - 所有 `_update_avatar(avatar_id, {"status": "..."})` 改为 `_update_avatar(avatar_id, AvatarUpdate(status=AvatarCloneStatus.XXX))` + - 删除裸字典辅助函数 `_update_avatar` 中的 `**obj_in` 展开,改用 `obj_in.model_dump(exclude_unset=True)` + +#### 验收标准 +- [ ] `grep -rn 'obj_in=\{' app/scheduler/handlers/avatar_handler.py` 返回为空 +- [ ] `mypy app/crud/` 无类型错误 +- [ ] `pytest` 通过 + +--- + +## 四、自动化防护网(Phase 5 之后部署) + +### 4.1 预提交钩子:禁词检查 +在 `.pre-commit-config.yaml` 或 `Makefile` 中增加 `lint-semantic`: + +```makefile +lint-semantic: + @echo "Checking semantic boundaries..." + @! grep -rn "element_id" app/api/ app/schemas/ app/scheduler/models.py | grep -v "provider_element_id" || (echo "ERROR: element_id leaked to upper layers"; exit 1) + @! grep -rn "kling_task_id" app/api/ app/schemas/ app/scheduler/models.py || (echo "ERROR: kling_task_id leaked to upper layers"; exit 1) + @! grep -rn "scene_desc" app/ | grep -v "__pycache__" || (echo "ERROR: scene_desc not fully renamed"; exit 1) + @! grep -rn "TaskRecord\|TaskRegistry\|running_task_ids" app/scheduler/ || (echo "ERROR: Scheduler task naming not fully migrated"; exit 1) + @! grep -rn "<<>>\|<<>>" app/scheduler/ app/services/ || (echo "ERROR: Kling prompt syntax leaked"; exit 1) + @echo "Semantic check passed" +``` + +### 4.2 mypy 渐进严格化 +- [ ] 在 `pyproject.toml` 中为 `app/scheduler/` 和 `app/schemas/` 单独开启 `strict = true` +- [ ] 逐步扩展至 `app/api/` 和 `app/services/` + +### 4.3 AGENTS.md 术语表(Glossary) +在 `AGENTS.md` 中新增"统一术语表"章节(见下文),所有 AI Agent 修改代码前必须查阅。 + +--- + +## 五、风险与回滚策略 + +| 风险 | 影响 | mitigation | +|------|------|-------------| +| Phase 1 删除 `ScriptShot` 影响前端类型生成 | 中 | `ScriptShot` 保留为 `Segment` 的 `TypeAlias` 一个 Sprint,待前端适配后删除 | +| Phase 2 `JobRegistry` 重命名导致 API 层引用遗漏 | 高 | 使用 IDE 全局重构(PyCharm/Ruff/Rename Symbol),执行后跑全量 `pytest` | +| Phase 3 DB 字段重命名需要数据迁移 | 中 | Alembic 脚本必须包含 `op.alter_column` 的 `existing_type` 和 `existing_nullable` | +| Phase 4 `script` 迁出 BackgroundTask 后响应时间变长 | 低 | 脚本生成仍立即返回 `job_id`,前端通过轮询 `/tasks/{job_id}` 获取结果,接口契约不变 | +| 多 Phase 并行开发导致冲突 | 高 | **严禁并行**:必须按 1→2→3→4→5 顺序执行,每个 Phase 合并到主分支后再开始下一个 | + +--- + +## 六、作为 GitHub Project Task List 的格式 + +若导入 GitHub Project,建议按以下结构建 5 个 Milestone: + +```markdown +### Milestone 1: Schema Unification +- [ ] #101 Create `app/schemas/segment.py` with `Segment` model +- [ ] #102 Create `app/schemas/enums.py` with `JobStatus`, `SegmentStatus`, `AvatarCloneStatus`, `KlingTaskStatus` +- [ ] #103 Create `app/schemas/job.py` with `JobParams` Union +- [ ] #104 Remove `ShotUnit` from `app/scheduler/models.py` +- [ ] #105 Remove `ShotTask` from `app/services/kling_video_service.py` +- [ ] #106 Remove `ShotData` from `app/api/v1/video.py` +- [ ] #107 Rename `scene_desc` → `scene` across codebase +- [ ] #108 Migrate all `status` strings to Enums + +### Milestone 2: Scheduler De-tasking +- [ ] #201 Rename `TaskRecord` → `JobRecord` +- [ ] #202 Rename `TaskRegistry` → `JobRegistry` +- [ ] #203 Registry auto-deserializes `JobParams` models +- [ ] #204 Replace `dict` changes with `StateChange` dataclass +- [ ] #205 Update all Handlers to return `list[StateChange]` + +### Milestone 3: Vendor Firewall +- [ ] #301 API layer: `element_id` → `human_id` +- [ ] #302 DB model: add `provider` field, rename task/element IDs +- [ ] #303 Scheduler model: `kling_task_id` → `provider_task_id` +- [ ] #304 Provider DTOs: `KlingVideoResult`, `KlingImageResult`, etc. +- [ ] #305 Move `KlingPromptBuilder` to Provider layer +- [ ] #306 Alembic migration for avatar table changes + +### Milestone 4: Cleanup & Unification +- [ ] #401 Remove legacy compatibility fields (`video_task_id`, `image_task_id`) +- [ ] #402 Fix historical naming (`cache_err`, `RateLimiter` docstrings, etc.) +- [ ] #403 Delete `app/services/dto.py` +- [ ] #404 Migrate `script` task from BackgroundTask to Scheduler + +### Milestone 5: CRUD Strong Typing +- [ ] #501 Create `AvatarCreate` / `AvatarUpdate` schemas +- [ ] #502 Type-constrain CRUDBase +- [ ] #503 Refactor `avatar_handler.py` to use `AvatarUpdate` instead of raw dicts +- [ ] #504 Add `lint-semantic` to Makefile / pre-commit +- [ ] #505 Update `AGENTS.md` with Glossary and layer rules +``` + +--- + +## 七、相关文档 + +- [统一异步调度器设计文档](./unified-async-scheduler.md) +- [数据库设计文档](./database-design.md) +- [AGENTS.md](../AGENTS.md)(术语表与分层禁令) diff --git a/docs/unified-async-scheduler.md b/docs/unified-async-scheduler.md new file mode 100644 index 0000000..9bc8d19 --- /dev/null +++ b/docs/unified-async-scheduler.md @@ -0,0 +1,350 @@ +# 统一异步任务调度方案 + +> **状态:已完成(2026-04-17)** — 本文档所述方案已全面实施,Celery 已完全移除,所有第三方异步任务现由 Async Engine Scheduler 统一调度。 + +> 本文档用于替代原 Celery 在"提交→轮询→收尾"类第三方异步任务中的角色,解决视频生成、形象克隆等任务在队列中频繁出现的拥堵、死锁和状态不一致问题。 + +--- + +## 1. 背景与问题 + +### 1.1 当前架构的缺陷 + +目前项目使用 Celery Worker 处理所有第三方异步任务,包括: + +- **视频生成** (`video`):提交 Kling 分镜 → 轮询状态 → 下载上传 +- **形象克隆** (`avatar_clone`):提交音色 → 轮询 → 提交主体 → 轮询 +- **字幕对齐** (`subtitle`) +- **图片生成** (`image`) + +这些任务都被放进 Celery 队列,由 Worker 并发消费。但 Kling 视频生成本质上是**"占用并发槽位并长时间等待"**的过程,当前设计存在三个结构性问题: + +1. **轮询任务风暴**:`poll_video_task` 用 Celery `retry(countdown=5)` 模拟轮询,一个 8 分钟的 Kling 任务会产生近百个 Celery Task,淹没队列调度器和 Redis Result Backend。 +2. **快慢任务混排**:下载上传(IO 密集型)和提交/轮询(轻量 HTTP)共用 `video` 队列,Worker 被长任务占满,新任务饿死。 +3. **状态死锁**:`download_upload_shot` 作为独立 Celery Task,一旦被 Worker 强制 Kill(如超时),shot 状态永远卡在 `downloading`,而轮询任务又不再处理它,导致整个任务假死。 + +### 1.2 核心认知 + +Kling 是一个有**严格并发上限**(20 槽位)的第三方异步执行池。我们需要的是一个 **Slot-Based Scheduler**(槽位调度器),而不是一个任务队列(Celery)。 + +> **任务队列**擅长"把独立任务尽快分发出去"; +> **槽位调度器**擅长"在有限资源下,周期性补货、轮询和收尾"。 + +Kling 视频生成和形象克隆属于后者。 + +--- + +## 2. 架构总览 + +``` +┌─────────────┐ HTTP ┌──────────────────┐ +│ Tauri App │ ◄────────────► │ FastAPI API │ +│ (React) │ │ (Gateway) │ +└─────────────┘ └────────┬─────────┘ + │ + ┌───────────────────┼───────────────────┐ + │ │ │ + ▼ ▼ ▼ + ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ + │ PostgreSQL │ │ Redis │ │ Object Store │ + │ (持久化/审计) │ │ (运行时状态) │ │ (七牛/本地) │ + └──────────────┘ └──────┬───────┘ └──────────────┘ + │ + ┌────────┴────────┐ + │ Async Engine │ + │ (Slot Scheduler)│ + │ python main.py │ + └────────┬────────┘ + │ + ┌──────────────────┼──────────────────┐ + ▼ ▼ ▼ + ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ + │ Video Handler│ │Avatar Handler│ │Future Handler│ + │ max_slots=18│ │ max_slots=2 │ │ (subtitle…) │ + └─────────────┘ └─────────────┘ └─────────────┘ +``` + +### 2.1 核心组件 + +| 组件 | 职责 | 技术选型 | +|------|------|----------| +| **FastAPI API** | 接收前端请求、创建任务、写入状态、供前端轮询 | 现有 FastAPI | +| **Redis** | 存储任务的**运行时状态**(running shots、当前 stage、slot 占用集合) | 现有 Redis | +| **PostgreSQL** | 存储任务的**持久化记录**(创建时间、最终结果、成本统计、失败原因) | 现有 PostgreSQL | +| **Async Engine** | 独立的调度进程,每 10 秒一次 **Tick**,驱动所有任务状态推进 | Python `asyncio` | +| **Handler** | 插件化模块,每个第三方平台一个实现 | 面向接口的 Python 类 | + +--- + +## 3. 核心机制 + +### 3.1 统一状态机 + +无论 video 还是 avatar_clone,所有第三方异步任务单元收敛到 **5 个统一状态**: + +``` +pending → submitted → succeed → completed + │ │ + └──────────────────────────────┘ + ↓ + failed +``` + +- **`pending`**:在队列里等待全局 slot 空闲 +- **`submitted`**:已占用 slot,已提交给 Kling,等待轮询结果 +- **`succeed`**:Kling 返回成功,Async Engine 立即触发下载/收尾(后台异步执行) +- **`failed`**:Kling 返回失败或提交异常 +- **`completed`**:下载、上传、DB 写入全部完成 + +对于 avatar_clone 这种**多阶段**任务,内部用 Sub-State 嵌套,但每个阶段仍遵循同一模式: + +``` +voice_pending → voice_submitted → voice_succeed + ↓ + element_pending → element_submitted → element_succeed → completed +``` + +### 3.2 Slot Manager(全局并发控制器) + +基于 **Redis SET + Lua 脚本** 实现严格的原子槽位管理: + +```python +class SlotManager: + async def acquire(self, slot_key: str, slot_id: str, max_slots: int) -> bool: + """Lua 脚本原子执行:SADD -> SCARD -> 超限则 SREM""" + lua = """ + local key = KEYS[1] + local slot_id = ARGV[1] + local max_slots = tonumber(ARGV[2]) + redis.call('sadd', key, slot_id) + local count = redis.call('scard', key) + if count > max_slots then + redis.call('srem', key, slot_id) + return 0 + end + redis.call('expire', key, 1800) + return 1 + """ + return await self.redis.eval(lua, 1, slot_key, slot_id, str(max_slots)) == 1 + + async def release(self, slot_key: str, slot_id: str) -> None: + await self.redis.srem(slot_key, slot_id) +``` + +当前配置: + +- **Video 槽位池**:`kling:video_slots`,上限 **18** +- **Avatar 槽位池**:`kling:avatar_slots`,上限 **2** + +> **为什么 Lua 脚本?** 确保 `SADD + SCARD + 条件 SREM` 原子执行。即使未来启动第二个 Scheduler 实例做 HA,也不会出现并发超发。 + +### 3.3 Async Engine Tick 循环 + +Scheduler 是一个独立的 `asyncio` 进程,主循环如下: + +```python +async def main(): + engine = AsyncEngine() + while True: + tick_start = time.monotonic() + + # 1. 加载所有 running 的任务 + tasks = await engine.registry.get_running_tasks() + + # 2. 按 Handler 分组,并行执行各自的 tick + changes = await asyncio.gather(*[ + handler.tick(tasks_for_handler, engine.slots) + for handler in engine.handlers.values() + ]) + + # 3. 批量应用状态变更(Pipeline 写入 Redis) + await engine.registry.apply_changes(flatten(changes)) + + # 4. 对 completed/failed 的任务,持久化到 PostgreSQL + await engine.persist_finished_tasks() + + # 5. 控制 Tick 间隔(固定 10 秒,执行过久时至少休息 2 秒) + elapsed = time.monotonic() - tick_start + await asyncio.sleep(max(10 - elapsed, 2)) +``` + +### 3.4 Handler 插件化接口 + +每个第三方平台实现一个 Handler: + +```python +class AsyncHandler(ABC): + name: str # e.g. "video" + slot_key: str # e.g. "kling:video_slots" + max_slots: int # e.g. 18 + + @abstractmethod + async def tick(self, tasks: list[Task], slots: SlotManager) -> list[StateChange]: + """每个 Tick 执行一次,返回需要更新的状态变更列表""" + pass +``` + +#### Video Handler 的 tick 逻辑 + +1. **查**:遍历所有 `submitted` 的 shots,批量并行查询 Kling 状态 +2. **收**: + - `succeed` → `release_slot` + `asyncio.create_task(download_and_upload(shot))` + 状态改为 `completed` + - `failed` → `release_slot` + 状态改为 `failed` +3. **补**:计算空闲槽位数,从 `pending` 队列 FIFO 取出新 shot 提交给 Kling,直到槽满 +4. **写**:更新 task 的聚合状态(completed/total/message)到 Redis + +#### Avatar Handler 的 tick 逻辑 + +1. 检查当前 stage(如 `voice_submitted`),查询 Kling 状态 +2. 若 `voice_succeed` → 释放 slot,推进到 `element_pending`,并在同一 tick 内尝试申请 slot 提交主体创建 +3. 若 `element_succeed` → 释放 slot,状态改为 `completed` + +--- + +## 4. API 层与数据流 + +### 4.1 创建任务(不变) + +```python +@router.post("/{task_type}", response_model=TaskCreateResponse) +async def create_task(task_type: str, request: TaskCreateRequest): + task_id = generate_task_id() + + # 1. 写入 PostgreSQL(持久化底单) + await db_task.create(task_id=task_id, type=task_type, user_id=...) + + # 2. 写入 Redis(标记为 pending,供 Async Engine 消费) + await redis_task.create(task_id=task_id, type=task_type, status="pending", ...) + + return TaskCreateResponse(task_id=task_id, status="pending") +``` + +### 4.2 查询状态(不变) + +```python +@router.get("/{task_id}", response_model=TaskStatusResponse) +async def get_task_status(task_id: str): + # 先读 Redis(热数据) + task = await redis_task.get(task_id) + if not task: + # fallback 到 PostgreSQL(已完成的归档数据) + task = await db_task.get(task_id) + return task +``` + +### 4.3 前端兼容性 + +**前端 `useTask.ts` 的轮询逻辑完全不需要修改。** 这是渐进式迁移的关键——调度层的重构对上层透明。 + +--- + +## 5. 部署方案 + +### 5.1 Docker Compose + +```yaml +services: + api: + build: + context: ../python-api + dockerfile: Dockerfile + command: uvicorn app.main:app --host 0.0.0.0 --port 8000 + # ... + + scheduler: + build: + context: ../python-api + dockerfile: Dockerfile + container_name: meijiaka-scheduler + command: python -m app.scheduler.main + environment: + - REDIS_HOST=redis + - DATABASE_URL=postgresql+asyncpg://... + depends_on: + - redis + - db + restart: unless-stopped + deploy: + resources: + limits: + memory: 512M +``` + +### 5.2 Celery 的处置 + +- 立即下线 `worker-video`(不再消费 `video` 队列) +- Phase 2 下线 `worker-avatar`(Avatar Handler 迁入 Async Engine 后) +- 可选:暂时保留 Celery 跑 `subtitle`,待后续迁移 +- 最终目标:所有"提交→轮询"类任务都迁入 Async Engine,Celery 整体移除 + +--- + +## 6. 迁移路径 + +| 阶段 | 时间 | 动作 | 风险 | +|------|------|------|------| +| **Phase 1** | 本周 | Async Engine 只接管 `video`(18 slots);`avatar_clone` 仍由 Celery 运行 | 改动面最小,只验证 video 链路 | +| **Phase 2** | 1-2 周后 | Async Engine 新增 `avatar_clone` Handler(2 slots);彻底下线 Celery 的 `worker-video` 和 `worker-avatar` | 验证 avatar 链路,解决资源饿死 | +| **Phase 3** | 未来 | `subtitle`、`image` 等陆续迁入 Async Engine;Celery 完全移除 | 统一所有第三方异步任务调度 | + +--- + +## 7. 设计原则论证 + +### 7.1 主流(Mainstream) + +- **Redis + PostgreSQL 双存储**:运行态在 Redis,持久态在 PostgreSQL。这是现代异步系统的事实标准,从 AWS Lambda 到 Vercel 再到国内云厂商均采用类似模式。 +- **Python asyncio 轻量调度器**:不引入 Kafka、RabbitMQ 或 Airflow 等重型框架,利用原生异步能力构建。Prefect、Dagster 的底层 Scheduler 也采用类似思想。 +- **Gateway + 独立 Scheduler 进程**:API 负责接入,Scheduler 负责推进,职责清晰。这是当前中小型 SaaS 的主流演进方向。 + +### 7.2 合理(Reasonable) + +- **完全匹配项目定位**:项目定位是"轻量云账号 + 全本地业务数据",不需要 Kubernetes 或复杂工作流引擎。Async Engine 只是一个额外的 Python 进程,资源占用 < 512MB。 +- **渐进式迁移,契约不变**:前端轮询逻辑、API URL、响应 Schema 均不变。改动仅集中在后端任务分发层,业务代码零侵入。 +- **资源隔离精确可控**:Video 18 slots + Avatar 2 slots = 20 slots,与 Kling 实际并发限制完全对齐。不会出现"形象克隆占满 Worker 导致视频饿死"的结构性问题。 +- **开发体验优先**:本地开发时,scheduler 可以和 api 一起 `docker-compose up`,也可以单独 `python -m app.scheduler.main` 调试。不需要 ngrok,不需要把开发环境搬到云端。 +- **幂等和可恢复**:每个 shot 的提交操作都是幂等的。Redis 记录了 `kling_task_id`,Scheduler 重启后从 Redis 恢复 running 任务,继续轮询,不会丢失状态。 + +### 7.3 长期稳定(Long-term Stable) + +- **HA 预留,无单点故障**:`SlotManager` 基于 Redis Lua 脚本实现原子操作,天然支持多实例竞争。未来如需高可用,可启动第二个 Scheduler 实例,通过 Redis 分布式锁选举 Leader,实现秒级主备切换,无需重构。 +- **Handler 插件化扩展**:未来接入即梦、Runway、Pika 或新的 AI 服务,只需实现新的 `AsyncHandler` 子类,配置 `slot_key` 和 `max_slots`。核心调度逻辑永远不需要改动。 +- **数据一致性保障**:运行态在 Redis(崩溃恢复快),完成态在 PostgreSQL(数据不丢)。即使 Scheduler 挂掉 30 分钟,Kling 端的任务仍在运行,恢复后继续轮询即可。 +- **第三方接口变更的防御性**:Handler 内部对 Kling API 的调用有统一的超时控制、重试策略和异常兜底。如果 Kling 某个接口升级,只改对应 Handler,不影响其他模块。 +- **可观测性支撑长期运维**:通过 Prometheus 指标,可长期监控"视频生成成功率"、"平均生成耗时"、"槽位利用率"、"Kling API 延迟分布",为后续扩容和成本优化提供数据支撑。 + +--- + +## 8. 关键文件位置(建议) + +``` +python-api/ +├── app/ +│ └── scheduler/ +│ ├── __init__.py +│ ├── main.py # Async Engine 入口(Tick 循环) +│ ├── engine.py # AsyncEngine 核心调度器 +│ ├── slot_manager.py # 槽位管理器(Redis Lua) +│ ├── registry.py # 任务注册表(Redis 读写) +│ ├── handlers/ +│ │ ├── __init__.py +│ │ ├── base.py # AsyncHandler 抽象基类 +│ │ ├── video_handler.py # Video 任务处理器 +│ │ └── avatar_handler.py # Avatar Clone 处理器 +│ └── models.py # Scheduler 内部数据模型 +``` + +--- + +## 9. 结论 + +当前 Celery 在"提交→轮询→收尾"类任务中的角色是**结构性错位**的。它带来的任务风暴、队列拥堵和状态死锁不是可以通过调参修复的,而是其模型与问题本质不匹配的结果。 + +**统一异步调度方案**的核心决策: + +1. **用 Async Engine(Slot-Based Scheduler)替代 Celery 管理所有第三方异步任务** +2. **Video 分配 18 槽,Avatar Clone 分配 2 槽,由唯一调度器全局管理** +3. **API 层和前端轮询逻辑完全不变,实现渐进式迁移** +4. **本地开发环境保持原样,无需引入 webhook 或云端部署** + +这是根治"任务队列生成视频总会出问题"的唯一长期方案。 diff --git a/docs/video-generation-flow.md b/docs/video-generation-flow.md new file mode 100644 index 0000000..a87989f --- /dev/null +++ b/docs/video-generation-flow.md @@ -0,0 +1,342 @@ +# 视频生成交互流程设计 + +## 一、正常流程(批量生成) + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Step 0: 检查前置条件 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 点击【生成视频】按钮 │ +│ │ │ +│ ▼ │ +│ ┌─────────────────┐ │ +│ │ 检查本地状态 │ │ +│ │ 如果正在生成中 │───► 提示"已有任务进行中,请等待完成" │ +│ └─────────────────┘ 或者"是否取消当前任务?" │ +│ │ │ +│ ▼ 无进行中任务 │ +│ ┌─────────────────┐ │ +│ │ 检查是否选形象 │ │ +│ │ 未选择 │───► 弹出形象选择弹窗 │ +│ └─────────────────┘ │ +│ │ │ +│ ▼ 已选择 │ +│ 继续下一步 │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ Step 1: 确认弹窗 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────┐ │ +│ │ 开始生成视频 │ │ +│ ├─────────────────────────────────────────┤ │ +│ │ │ │ +│ │ 将生成 8 个分镜视频 │ │ +│ │ 预计耗时:约 15-20 分钟 │ │ +│ │ │ │ +│ │ ⚠️ 生成过程中请勿关闭应用 │ │ +│ │ 您可以最小化窗口,但不要关闭 │ │ +│ │ │ │ +│ │ ┌────────────┐ ┌──────────────────┐ │ │ +│ │ │ 取消 │ │ 开始生成 │ │ │ +│ │ └────────────┘ └──────────────────┘ │ │ +│ │ │ │ +│ └─────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ Step 2: 进入生成状态(界面锁定) │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 【生成】按钮变为【生成中...】且 disabled │ +│ │ +│ 顶部显示全局状态栏: │ +│ ┌─────────────────────────────────────────────────────────────┐│ +│ │ 🎬 视频生成中 ━━━━━━━━⏳━━━━ 预计还需 12 分钟 [?] ││ +│ └─────────────────────────────────────────────────────────────┘│ +│ │ +│ 显示模态弹窗(不可关闭): │ +│ ┌─────────────────────────────────────────┐ │ +│ │ 视频生成 │ │ +│ ├─────────────────────────────────────────┤ │ +│ │ │ │ +│ │ ┌─────────────┐ │ │ +│ │ │ 状态标签 │ │ │ +│ │ │ 任务已开启 │ │ │ +│ │ └─────────────┘ │ │ +│ │ │ │ +│ │ 正在为空镜生成参考图片... │ │ +│ │ │ │ +│ │ 预计还需 12 分钟 │ │ +│ │ │ │ +│ │ [最小化到后台] │ │ +│ │ │ │ +│ └─────────────────────────────────────────┘ │ +│ │ +│ 界面锁定状态: │ +│ - 禁用:生成按钮、新建项目、添加/删除分镜 │ +│ - 可浏览:但不能修改任何内容 │ +│ - 可退出应用:但会提示"任务将后台继续,确定退出?" │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ Step 3: 状态流转 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 状态标签流转: │ +│ │ +│ 分镜(omni-video): │ +│ 任务已开启 ──► 排队生成中 ──► 任务已完成 │ +│ │ +│ 空镜(文生图+图生视频): │ +│ 任务已开启 ──► 生成参考图片... ──► 排队生成中 ──► 任务已完成 │ +│ │ +│ 详细描述文字实时更新(SSE 推送): │ +│ - "正在初始化任务..." │ +│ - "正在为空镜生成参考图片..." │ +│ - "图片生成完成,开始生成视频..." │ +│ - "正在生成视频,请稍候..." │ +│ - "已完成 3/8 个分镜" │ +│ - "整理生成结果..." │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ Step 4: 完成 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 模态弹窗更新: │ +│ ┌─────────────────────────────────────────┐ │ +│ │ 视频生成 │ │ +│ ├─────────────────────────────────────────┤ │ +│ │ │ │ +│ │ ┌─────────────┐ │ │ +│ │ │ 任务已完成 │ │ │ +│ │ └─────────────┘ │ │ +│ │ │ │ +│ │ 成功生成 8 个视频 │ │ +│ │ │ │ +│ │ ┌──────────────────────────────────┐ │ │ +│ │ │ 确定 │ │ │ +│ │ └──────────────────────────────────┘ │ │ +│ │ │ │ +│ └─────────────────────────────────────────┘ │ +│ │ +│ 用户点击【确定】: │ +│ 1. 关闭弹窗 │ +│ 2. 解锁界面 │ +│ 3. 自动滚动到第一个有视频的分镜 │ +│ 4. 播放第一个视频 │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## 二、单个重新生成流程 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 差异点 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 入口:分镜卡片上的【重新生成】按钮 │ +│ │ +│ 确认弹窗简化: │ +│ ┌─────────────────────────────────────────┐ │ +│ │ 重新生成视频 │ │ +│ ├─────────────────────────────────────────┤ │ +│ │ 将重新生成分镜 3 的视频 │ │ +│ │ 预计耗时:约 3-5 分钟 │ │ +│ │ │ │ +│ │ ⚠️ 生成过程中请勿关闭应用 │ │ +│ │ │ │ +│ │ [取消] [确定] │ │ +│ └─────────────────────────────────────────┘ │ +│ │ +│ 完成后:自动选中该分镜并播放 │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## 三、异常流程 + +### 3.1 用户尝试关闭应用 + +``` +用户点击关闭窗口(或 Cmd+Q / Alt+F4) + │ + ▼ +┌─────────────────────────────────────────┐ +│ ⚠️ 确认关闭 │ +├─────────────────────────────────────────┤ +│ │ +│ 视频生成任务仍在进行中 │ +│ │ +│ 如果选择关闭: │ +│ - 任务将在后台继续运行 │ +│ - 生成完成后会推送系统通知 │ +│ - 下次打开应用可查看结果 │ +│ │ +│ [取消] [最小化到托盘] [关闭应用] │ +│ │ +└─────────────────────────────────────────┘ +``` + +### 3.2 应用崩溃/强制退出后恢复 + +``` +用户重新打开应用 + │ + ▼ +┌─────────────────────────────────────────┐ +│ 📋 恢复未完成任务 │ +├─────────────────────────────────────────┤ +│ │ +│ 检测到上次有未完成的视频生成任务 │ +│ │ +│ 项目:厨房改造方案 │ +│ 进度:已完成 5/8 个分镜 │ +│ 状态:仍在后台处理中 │ +│ │ +│ [查看进度] [我知道了] │ +│ │ +└─────────────────────────────────────────┘ + +点击【查看进度】: +- 跳转到视频生成页面 +- 自动恢复进度弹窗显示 +- 继续监听 SSE/轮询 +``` + +### 3.3 生成失败 + +``` +┌─────────────────────────────────────────┐ +│ ❌ 生成失败 │ +├─────────────────────────────────────────┤ +│ │ +│ 视频生成过程中发生错误 │ +│ │ +│ 错误信息:Kling API 超时 │ +│ │ +│ 已生成的视频已保存 │ +│ 失败的分镜:分镜3、分镜7 │ +│ │ +│ [返回查看] [重试失败项] │ +│ │ +└─────────────────────────────────────────┘ + +点击【重试失败项】: +- 只重新生成失败的那几个分镜 +- 复用现有参数 +``` + +### 3.4 网络断开 + +``` +SSE 连接断开 + │ + ▼ +状态栏显示:"网络异常,正在重连...(1/3)" + │ + ▼ +自动重连 SSE(最多 3 次) + │ + ├─► 重连成功:继续接收进度 + │ + └─► 重连失败:切换到轮询模式 + │ + ▼ + 每 5 秒轮询一次状态 + │ + ▼ + 网络恢复后:自动切回 SSE +``` + +## 四、本地状态管理 + +```typescript +// localStorage: meijiaka_generation_state +interface GenerationState { + // 任务标识 + jobId: string; + projectId: string; + + // 任务状态 + status: 'pending' | 'generating' | 'completed' | 'failed'; + + // 任务信息(用于恢复显示) + shots: Array<{ + id: string; + type: 'segment' | 'empty_shot'; + }>; + totalShots: number; + + // 时间戳 + startedAt: number; + lastUpdatedAt: number; + + // 结果(完成后填写) + results?: Array<{ + shotId: string; + status: 'completed' | 'failed'; + videoPath?: string; + errorMessage?: string; + }>; + + // 错误信息 + errorMessage?: string; +} +``` + +## 五、状态流转图 + +``` + ┌─────────────┐ + │ IDLE │ + └──────┬──────┘ + │ 点击生成 + ▼ + ┌─────────────┐ + ┌───────────────►│ CONFIRM │◄───────────────┐ + │ │ 确认弹窗 │ │ + │ └──────┬──────┘ │ + │ 取消 │ 确认 │ + │ ▼ │ + │ ┌─────────────┐ │ + │ │ GENERATING │────────────────┤ + │ │ 生成中 │ 应用崩溃/关闭 │ + │ └──────┬──────┘ │ + │ │ │ + │ ┌────────────┼────────────┐ │ + │ │ │ │ │ + │ ▼ ▼ ▼ │ + │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ + │ │SUCCESS │ │ FAILED │ │ TIMEOUT │ │ + │ └────┬────┘ └────┬────┘ └────┬────┘ │ + │ │ │ │ │ + │ ▼ └────────────┘ │ + │ ┌─────────┐ │ │ + └───┤ RESULT │◄──────────────┘ │ + │ 结果弹窗 │ │ + └────┬────┘ │ + │ │ + ▼ │ + ┌─────────┐ │ + │ IDLE │───────────────────────────────┘ + └─────────┘ 下次启动检测恢复 +``` + +## 六、关键决策点 + +| 决策 | 选择 | 理由 | +|------|------|------| +| 生成中能否关闭应用 | ✅ 可以,但提示后台继续 | 用户有急事时需要关闭 | +| 生成中能否切换项目 | ❌ 不能 | 避免状态混乱 | +| 生成中能否修改脚本 | ❌ 不能 | 避免参数不一致 | +| 失败后能否重试 | ✅ 可以,只重试失败的 | 减少重复等待 | +| 是否需要系统通知 | ✅ 需要(第二阶段) | 用户最小化后能感知完成 | diff --git a/docs/volcengine-video-caption-api.md b/docs/volcengine-video-caption-api.md new file mode 100644 index 0000000..6e32f69 --- /dev/null +++ b/docs/volcengine-video-caption-api.md @@ -0,0 +1,201 @@ +# 火山引擎音视频字幕 API 开发文档 + +> 更新日期: 2026-04-09 +> 官方文档: https://www.volcengine.com/docs/6561/80907 + +--- + +## 产品简介 + +火山引擎音视频字幕服务提供两种能力: + +1. **音视频字幕生成** - 自动识别音频中的语音/歌词,生成带时间轴的字幕 +2. **自动字幕打轴** - 为已有字幕文本自动配上时间轴 + +--- + +## 基础信息 + +| 项目 | 内容 | +|------|------| +| 基础 URL | `https://openspeech.bytedance.com/api/v1/vc` | +| 鉴权 Header | `Authorization: Bearer; {token}` | +| 文件限制 | ≤200MB, 支持 WAV/M4A/MP3/MP4/MOV/OGG | + +--- + +## API 接口 + +### 1. 音视频字幕生成 + +#### 提交任务 +```http +POST /submit?appid={appid}&language=zh-CN&use_punc=True +Content-Type: application/json +Authorization: Bearer; {token} + +{"url": "https://example.com/audio.mp3"} +``` + +**关键参数:** +- `language` - 语言: `zh-CN`, `en-US`, `ja-JP`, `ko-KR`, `es-MX`, `ru-RU`, `fr-FR`, `yue`, `wuu`, `nan`, `ug` +- `caption_type` - 识别类型: `auto`(默认), `speech`, `singing` +- `use_punc` - 自动标点: `True`, `False` +- `use_itn` - 数字转换: `True`(中文数字转阿拉伯数字) +- `words_per_line` - 每行字数, 默认 46 +- `max_lines` - 每屏行数, 默认 1 + +#### 查询结果 +```http +GET /query?appid={appid}&id={task_id}&blocking=1 +Authorization: Bearer; {token} +``` + +**响应:** +```json +{ + "code": 0, + "message": "Success", + "duration": 5.32, + "utterances": [ + { + "text": "识别文本", + "start_time": 0, + "end_time": 3197, + "words": [ + {"text": "单字", "start_time": 0, "end_time": 208} + ] + } + ] +} +``` + +--- + +### 2. 自动字幕打轴 + +#### 提交任务 +```http +POST /ata/submit?appid={appid}&caption_type=speech +Content-Type: application/json +Authorization: Bearer; {token} + +{ + "url": "https://example.com/audio.mp3", + "audio_text": "这是要被打轴的字幕文本" +} +``` + +**参数:** +- `caption_type` - `speech`(说话) 或 `singing`(歌词) +- `sta_punc_mode` - 标点模式: `1`(省略句末标点), `2`(空格代替), `3`(保留完整标点) + +#### 查询结果 +```http +GET /ata/query?appid={appid}&id={task_id}&blocking=1 +Authorization: Bearer; {token} +``` + +--- + +## 错误码 + +| 码 | 含义 | 处理 | +|----|------|------| +| 0 | 成功 | - | +| 2000 | 处理中 | 继续轮询 | +| 1001 | 参数无效 | 检查必填参数 | +| 1002 | 无权限 | 检查 token | +| 1003 | 超频 | 降低调用频率 | +| 1010 | 音频过长 | 缩短音频 | +| 1011 | 音频过大 | 压缩音频(<200MB) | +| 1012 | 格式无效 | 检查音频格式 | +| 1013 | 音频静音 | 检查音频内容 | + +--- + +## Python 代码示例 + +```python +import requests +import time + +TOKEN = "your_token" +APPID = "your_appid" +BASE_URL = "https://openspeech.bytedance.com/api/v1/vc" + +def submit(audio_url, language="zh-CN", use_punc=True): + """提交字幕生成任务""" + resp = requests.post( + f"{BASE_URL}/submit", + params={"appid": APPID, "language": language, "use_punc": str(use_punc)}, + json={"url": audio_url}, + headers={"Authorization": f"Bearer; {TOKEN}"} + ) + return resp.json()["id"] + +def query(task_id): + """查询任务结果""" + resp = requests.get( + f"{BASE_URL}/query", + params={"appid": APPID, "id": task_id, "blocking": "1"}, + headers={"Authorization": f"Bearer; {TOKEN}"} + ) + return resp.json() + +def generate_caption(audio_url, language="zh-CN"): + """完整流程: 提交->轮询->返回结果""" + task_id = submit(audio_url, language) + + for _ in range(60): # 最多轮询60秒 + result = query(task_id) + if result["code"] == 0: + return result["utterances"] + elif result["code"] != 2000: + raise Exception(f"Task failed: {result['message']}") + time.sleep(1) + + raise Exception("Timeout") + +def to_srt(utterances): + """转换为 SRT 字幕格式""" + def ms_to_time(ms): + h = ms // 3600000 + m = (ms % 3600000) // 60000 + s = (ms % 60000) // 1000 + ms = ms % 1000 + return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}" + + lines = [] + for i, u in enumerate(utterances, 1): + lines.append(f"{i}") + lines.append(f"{ms_to_time(u['start_time'])} --> {ms_to_time(u['end_time'])}") + lines.append(u['text']) + lines.append("") + return "\n".join(lines) + +# 使用示例 +if __name__ == "__main__": + utterances = generate_caption("https://example.com/audio.mp3") + srt_content = to_srt(utterances) + print(srt_content) +``` + +--- + +## cURL 示例 + +```bash +# 1. 提交任务 +TASK_ID=$(curl -s -X POST \ + -H "Authorization: Bearer; ${TOKEN}" \ + -H "content-type: application/json" \ + -d '{"url": "'${AUDIO_URL}'"}' \ + "https://openspeech.bytedance.com/api/v1/vc/submit?appid=${APPID}&language=zh-CN" \ + | jq -r '.id') + +# 2. 查询结果 +curl -s -X GET \ + -H "Authorization: Bearer; ${TOKEN}" \ + "https://openspeech.bytedance.com/api/v1/vc/query?appid=${APPID}&id=${TASK_ID}&blocking=1" +``` diff --git a/python-api/.env.example b/python-api/.env.example new file mode 100644 index 0000000..f86179c --- /dev/null +++ b/python-api/.env.example @@ -0,0 +1,72 @@ +# 美家卡智影 API - 环境变量配置示例 +# ================================ +# 复制此文件为 .env 并填写实际值 + +# === 基础配置 === +APP_NAME=美家卡智影 API +APP_VERSION=0.1.0 +DEBUG=true +ENV=development +HOST=0.0.0.0 +PORT=8000 + +# === 数据库配置 === +DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/meijiaka + +# === Redis 配置 === +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_DB=0 +# REDIS_PASSWORD= # 如无密码请留空或注释 + +# === JWT 安全配置 === +# 生产环境必须修改为强随机密钥 +SECRET_KEY=your-secret-key-here-change-in-production +ACCESS_TOKEN_EXPIRE_MINUTES=10080 +ALGORITHM=HS256 + +# === CORS 配置 === +CORS_ORIGINS=http://localhost:1420,http://127.0.0.1:1420,http://localhost:8080 + +# === AI 平台配置 === + +# 火山方舟(必需) +VOLCENGINE_API_KEY=your-volcengine-api-key +VOLCENGINE_BASE_URL=https://ark.cn-beijing.volces.com/api/v3 + +# 火山字幕服务(必需) +VOLCENGINE_CAPTION_APPID=your-caption-appid +VOLCENGINE_CAPTION_TOKEN=your-caption-token + +# 可灵 AI(必需,用于视频生成) +KLINGAI_ACCESS_KEY=your-kling-access-key +KLINGAI_SECRET_KEY=your-kling-secret-key + +# OpenAI(可选) +# OPENAI_API_KEY=sk-your-openai-key +# OPENAI_BASE_URL=https://api.openai.com/v1 + +# 文心一言(可选) +# WENXIN_API_KEY=your-wenxin-key +# WENXIN_SECRET_KEY=your-wenxin-secret + +# 通义千问(可选) +# QIANWEN_API_KEY=your-qianwen-key + +# === 七牛云存储(必需,用于空镜图片上传)=== +QINIU_ACCESS_KEY=your-qiniu-access-key +QINIU_SECRET_KEY=your-qiniu-secret-key +QINIU_VIDEO_BUCKET=media-liche +QINIU_VIDEO_DOMAIN=media.liche.cn +QINIU_IMAGE_BUCKET=img-liche +QINIU_IMAGE_DOMAIN=img.liche.cn + +# === 其他服务 === + +# AnyToCopy 文案提取(可选) +ANYTOCOPY_API_KEY=your-anytocopy-api-key +ANYTOCOPY_API_SECRET=your-anytocopy-secret +ANYTOCOPY_BASE_URL=https://api.anytocopy.com/vip/open-api/v1 + +# === 日志配置 === +LOG_LEVEL=INFO diff --git a/python-api/.gitignore b/python-api/.gitignore new file mode 100644 index 0000000..578177f --- /dev/null +++ b/python-api/.gitignore @@ -0,0 +1,75 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual Environment +venv/ +env/ +ENV/ +.venv/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store + +# Environment variables +.env +.env.local +.env.*.local + +# Database +*.db +*.sqlite3 + +# Logs +*.log +logs/ + +# Test coverage +htmlcov/ +.coverage +.pytest_cache/ +.tox/ + +# Alembic 迁移(保留脚本,忽略临时文件) +alembic/versions/*.pyc + +# Celery +celerybeat-schedule + +# Redis +dump.rdb + +# Docker +.dockerignore + +# Local development +local/ +temp/ +tmp/ + +# Data files +data/ + diff --git a/python-api/.pre-commit-config.yaml b/python-api/.pre-commit-config.yaml new file mode 100644 index 0000000..e86e12c --- /dev/null +++ b/python-api/.pre-commit-config.yaml @@ -0,0 +1,44 @@ +# 美家卡智影 - Git 钩子配置 +# 安装: pre-commit install +# 手动运行: pre-commit run --all-files + +repos: + # 代码格式化 + - repo: https://github.com/psf/black + rev: 24.10.0 + hooks: + - id: black + language_version: python3.13 + + # 代码检查 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.0 + hooks: + - id: ruff + args: [--fix] + + # TODO: 修复历史遗留类型错误后重新启用 + # 类型检查(暂时禁用) + # - repo: https://github.com/pre-commit/mirrors-mypy + # rev: v1.14.0 + # hooks: + # - id: mypy + # additional_dependencies: [types-PyYAML] + + # 安全扫描(暂时禁用) + # - repo: https://github.com/PyCQA/bandit + # rev: 1.8.0 + # hooks: + # - id: bandit + # args: ["-c", "pyproject.toml"] + # additional_dependencies: ["bandit[toml]"] + + # 依赖锁定文件同步检查 + - repo: local + hooks: + - id: uv-lock-check + name: Check uv lock file is up-to-date + entry: bash -c 'uv pip compile pyproject.toml -o requirements.lock --locked' + language: system + files: ^(pyproject\.toml|requirements\.lock)$ + pass_filenames: false diff --git a/python-api/.python-version b/python-api/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/python-api/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/python-api/.qiniu_pythonsdk_hostscache.json b/python-api/.qiniu_pythonsdk_hostscache.json new file mode 100644 index 0000000..6cd2d8f --- /dev/null +++ b/python-api/.qiniu_pythonsdk_hostscache.json @@ -0,0 +1 @@ +{"http:Pn60lJXcaOGKvMjn5qv-OMr7wR1lp1p8QG7Ul6NK:media-liche": {"upHosts": ["http://upload-z2.qiniup.com", "http://up-z2.qiniup.com"], "ioHosts": ["http://iovip-z2.qbox.me"], "rsHosts": ["http://rs-z2.qbox.me"], "rsfHosts": ["http://rsf-z2.qbox.me"], "apiHosts": ["http://api-z2.qiniu.com"], "deadline": 1776740815}, "http:Pn60lJXcaOGKvMjn5qv-OMr7wR1lp1p8QG7Ul6NK:img-liche": {"upHosts": ["http://upload-z2.qiniup.com", "http://up-z2.qiniup.com"], "ioHosts": ["http://iovip-z2.qbox.me"], "rsHosts": ["http://rs-z2.qbox.me"], "rsfHosts": ["http://rsf-z2.qbox.me"], "apiHosts": ["http://api-z2.qiniu.com"], "deadline": 1776433218}} \ No newline at end of file diff --git a/python-api/Dockerfile b/python-api/Dockerfile new file mode 100644 index 0000000..cce032c --- /dev/null +++ b/python-api/Dockerfile @@ -0,0 +1,44 @@ +# 美家卡智影 API - Docker 镜像 (使用 uv 优化) +# =========================================== + +FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS builder + +# 设置 uv 环境变量 +ENV UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + UV_PYTHON_DOWNLOADS=never + +WORKDIR /app + +# 先复制锁定文件,利用 Docker 缓存层 +COPY requirements.lock pyproject.toml ./ + +# 创建虚拟环境并安装依赖(利用 uv 的速度优势) +RUN uv venv /opt/venv && \ + uv pip sync --python /opt/venv/bin/python requirements.lock + +# 复制应用代码 +COPY app/ ./app/ + +# 安装应用本身(不安装 dev 依赖) +RUN uv pip install --python /opt/venv/bin/python --no-deps -e . + +# ===== 生产镜像 ===== +FROM python:3.13-slim AS production + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PATH="/opt/venv/bin:$PATH" + +# 从 builder 复制虚拟环境 +COPY --from=builder /opt/venv /opt/venv + +WORKDIR /app + +# 复制应用代码 +COPY app/ ./app/ +COPY pyproject.toml . + +EXPOSE 8000 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/python-api/Makefile b/python-api/Makefile new file mode 100644 index 0000000..9dfd4b3 --- /dev/null +++ b/python-api/Makefile @@ -0,0 +1,140 @@ +# 美家卡智影 API - 常用命令 +# ========================== + +.PHONY: help install dev install-hooks update-lock lint format test security clean docker + +help: ## 显示帮助信息 + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +# ========== 依赖管理 ========== + +install: ## 安装生产依赖(使用 lock 文件) + uv pip sync requirements.lock + +dev: ## 安装开发依赖(包含 dev extras) + uv pip install -e ".[dev]" + pre-commit install + +install-hooks: ## 安装 Git pre-commit 钩子 + pre-commit install + +update-lock: ## 更新 requirements.lock(修改 pyproject.toml 后执行) + uv pip compile pyproject.toml -o requirements.lock --upgrade + +update-lock-no-upgrade: ## 重新生成 lock 文件(不升级版本) + uv pip compile pyproject.toml -o requirements.lock + +# ========== 代码质量 ========== + +lint: ## 运行代码检查 (ruff + mypy) + ruff check app/ + mypy app/ + +format: ## 格式化代码 (black + ruff) + black app/ + ruff check --fix app/ + +format-check: ## 检查代码格式(不修改) + black --check app/ + ruff check app/ + +# ========== 测试 ========== + +test: ## 运行测试 + pytest -v + +test-cov: ## 运行测试并生成覆盖率报告 + pytest --cov=app --cov-report=html --cov-report=term + +# ========== 安全扫描 ========== + +security: ## 运行安全扫描 (bandit + pip-audit) + @echo "🔍 运行 Bandit 安全扫描..." + bandit -r app/ -c pyproject.toml + @echo "🔍 运行依赖漏洞扫描..." + pip-audit + +# ========== 开发服务器 ========== + +run: ## 启动开发服务器 + uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 + +scheduler: ## 启动 Async Engine Scheduler + python -m app.scheduler.main + +# ========== Docker ========== + +docker: ## 构建 Docker 镜像 + docker build -t meijiaka-api:latest . + +docker-run: ## 使用 Docker Compose 启动全部服务 + docker-compose up -d + +docker-logs: ## 查看 Docker 日志 + docker-compose logs -f + +docker-down: ## 停止 Docker 服务 + docker-compose down + +# ========== 清理 ========== + +clean: ## 清理缓存文件 + find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true + find . -type f -name "*.pyc" -delete 2>/dev/null || true + find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true + find . -type d -name ".mypy_cache" -exec rm -rf {} + 2>/dev/null || true + rm -rf htmlcov/ .coverage 2>/dev/null || true + +# ========== 语义层防护网 ========== + +lint-semantic: ## 语义层禁词检查(防止供应商术语泄漏到业务层) + @echo "🔍 检查 Layer 3+ 是否泄漏供应商术语..." + @# API 层(除 klingai Provider 代理)禁止 element_id 作为字段/参数名 + @errs=$$(grep -rn 'element_id' app/api --include='*.py' \ + | grep -v 'klingai.py' \ + | grep -v 'provider_element_id' \ + | grep -v '__pycache__' \ + | grep -v '#' \ + | grep -v '".*element_id.*"' \ + | grep -v "'.*element_id.*'"); \ + if [ -n "$$errs" ]; then \ + echo "$$errs"; \ + echo "❌ API 层发现 element_id(应使用 provider_element_id 或 human_id)"; \ + exit 1; \ + fi + @# Scheduler 层禁止 task_id 作为内部变量/Redis key(读取 Provider 返回除外) + @errs=$$(grep -rn '\btask_id\b' app/scheduler --include='*.py' \ + | grep -v 'job_id' \ + | grep -v '__pycache__' \ + | grep -v '\.get("task_id")' \ + | grep -v 'result.get("task_id")' \ + | grep -v 'task_type' \ + | grep -v '"task_id"' \ + | grep -v "'task_id'"); \ + if [ -n "$$errs" ]; then \ + echo "$$errs"; \ + echo "❌ Scheduler 层发现 task_id(应使用 job_id)"; \ + exit 1; \ + fi + @# 全局禁止 kling_task_id 作为持久化字段 + @errs=$$(grep -rn 'kling_task_id' app --include='*.py' \ + | grep -v '__pycache__' \ + | grep -v 'providers/klingai'); \ + if [ -n "$$errs" ]; then \ + echo "$$errs"; \ + echo "❌ 发现 kling_task_id(应使用 provider_task_id)"; \ + exit 1; \ + fi + @# Scheduler 层 Redis key 必须使用 job: 而非 task: + @errs=$$(grep -rn 'redis.*task:' app/scheduler --include='*.py' \ + | grep -v '__pycache__'); \ + if [ -n "$$errs" ]; then \ + echo "$$errs"; \ + echo "❌ Scheduler Redis key 使用 task:(应使用 job:)"; \ + exit 1; \ + fi + @echo "✅ 语义层检查通过" + +# ========== CI 检查 ========== + +ci: format-check lint lint-semantic test security ## 运行所有 CI 检查 diff --git a/python-api/README.md b/python-api/README.md new file mode 100644 index 0000000..324c807 --- /dev/null +++ b/python-api/README.md @@ -0,0 +1,166 @@ +# 美家卡智影 API + +美家卡智影后端服务 - 基于 FastAPI + PostgreSQL + Redis 的 AI 视频创作 API。 + +## 技术栈 + +| 组件 | 技术 | 版本 | +|------|------|------| +| Web 框架 | FastAPI | ^0.110.0 | +| 数据库 | PostgreSQL | 15+ | +| ORM | SQLAlchemy | 2.0+ (异步) | +| 缓存/状态 | Redis | 7.x | +| 异步调度 | Async Engine (Slot Scheduler) | Python asyncio | +| 部署 | Docker + Docker Compose | - | + +## 快速开始 + +### 1. 环境准备 + +确保已安装: +- Python 3.11+ +- Docker & Docker Compose(推荐) +- 或本地 PostgreSQL + Redis + +### 2. 使用 Docker Compose 启动(推荐) + +```bash +# 1. 克隆项目后进入目录 +cd python-api + +# 2. 复制环境变量配置 +cp .env.example .env + +# 3. 启动所有服务 +docker-compose up -d + +# 4. 查看日志 +docker-compose logs -f api + +# 5. 服务地址 +# API: http://localhost:8080 +# 文档: http://localhost:8080/docs +``` + +### 3. 本地开发 + +```bash +# 1. 创建虚拟环境 +python -m venv venv +source venv/bin/activate # Windows: venv\Scripts\activate + +# 2. 安装依赖 +pip install -e ".[dev]" + +# 3. 配置环境变量 +cp .env.example .env +# 编辑 .env,修改数据库连接等配置 + +# 4. 启动 PostgreSQL 和 Redis(Docker) +docker-compose up -d db redis + +# 5. 启动开发服务器 +uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 + +# 7. 启动 Async Engine Scheduler(另开终端) +python -m app.scheduler.main +``` + +## 项目结构 + +``` +python-api/ +├── app/ # 主应用代码 +│ ├── api/v1/ # API 路由 +│ ├── core/ # 核心工具(安全、异常) +│ ├── db/ # 数据库配置 +│ ├── models/ # SQLAlchemy 模型 +│ ├── schemas/ # Pydantic Schema +│ ├── services/ # 业务逻辑 +│ ├── scheduler/ # Async Engine 异步任务调度 +│ ├── ai/ # AI 模型相关 +│ ├── utils/ # 工具函数 +│ ├── config.py # 配置管理 +│ └── main.py # FastAPI 入口 +├── docker-compose.yml # Docker 编排 +├── Dockerfile # Docker 镜像 +├── pyproject.toml # 项目依赖 +└── README.md # 本文档 +``` + +## 数据模型 + +### 核心实体 + +- **User** - 用户/设备(设备 ID + JWT 认证) +- **Project** - 视频创作项目 +- **ScriptSegment** - 脚本分镜 +- **MediaAsset** - 媒体元数据(音频/视频/封面) +- **TaskQueue** - 异步任务队列 + +## API 路由 + +### 已实现 + +| 方法 | 路径 | 说明 | +|------|------|------| +| POST | `/api/v1/auth/login` | 设备登录/注册 | +| GET | `/api/v1/auth/me` | 获取当前用户 | +| GET | `/api/v1/system/health` | 健康检查 | +| GET | `/api/v1/system/version` | 版本信息 | + +### 待实现(M2-M5) + +- `/api/v1/script/*` - 脚本生成(SSE 流式) +- `/api/v1/voice/*` - 语音合成(TTS) +- `/api/v1/video/*` - 数字人视频(异步任务) +- `/api/v1/project/*` - 项目云同步 +- `/api/v1/parser/*` - 视频链接解析(预留) + +## 环境变量 + +见 `.env.example`,主要配置项: + +| 变量 | 说明 | 默认值 | +|------|------|--------| +| `DATABASE_URL` | PostgreSQL 连接字符串 | `postgresql+asyncpg://postgres:postgres@localhost:5432/meijiaka` | +| `REDIS_URL` | Redis 连接字符串 | `redis://localhost:6379/0` | +| `SECRET_KEY` | JWT 签名密钥 | 必须修改 | +| `OPENAI_API_KEY` | OpenAI API Key | - | +| `CORS_ORIGINS` | 允许的跨域来源 | `http://localhost:1420` | + +## 开发规范 + +### 代码风格 + +```bash +# 格式化 +black app/ + +# 检查 +ruff check app/ +mypy app/ + +# 测试 +pytest +``` + +### 提交规范 + +- `feat:` 新功能 +- `fix:` 修复 +- `docs:` 文档 +- `refactor:` 重构 +- `test:` 测试 + +## 与前端集成 + +Tauri 前端默认连接 `http://127.0.0.1:8080/api/v1`。 + +云端部署后: +1. 修改前端 `src/api/client.ts` 中的 `PYTHON_API_BASE_URL` +2. 更新 `tauri.conf.json` CSP 配置,添加云端域名到 `connect-src` + +## 许可 + +MIT diff --git a/python-api/alembic.ini b/python-api/alembic.ini new file mode 100644 index 0000000..f7f8540 --- /dev/null +++ b/python-api/alembic.ini @@ -0,0 +1,150 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts. +# this is typically a path given in POSIX (e.g. forward slashes) +# format, relative to the token %(here)s which refers to the location of this +# ini file +script_location = %(here)s/alembic + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s +# Or organize into date-based subdirectories (requires recursive_version_locations = true) +# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. for multiple paths, the path separator +# is defined by "path_separator" below. +prepend_sys_path = . + + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the tzdata library which can be installed by adding +# `alembic[tz]` to the pip requirements. +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to /versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "path_separator" +# below. +# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions + +# path_separator; This indicates what character is used to split lists of file +# paths, including version_locations and prepend_sys_path within configparser +# files such as alembic.ini. +# The default rendered in new alembic.ini files is "os", which uses os.pathsep +# to provide os-dependent path splitting. +# +# Note that in order to support legacy alembic.ini files, this default does NOT +# take place if path_separator is not present in alembic.ini. If this +# option is omitted entirely, fallback logic is as follows: +# +# 1. Parsing of the version_locations option falls back to using the legacy +# "version_path_separator" key, which if absent then falls back to the legacy +# behavior of splitting on spaces and/or commas. +# 2. Parsing of the prepend_sys_path option falls back to the legacy +# behavior of splitting on spaces, commas, or colons. +# +# Valid values for path_separator are: +# +# path_separator = : +# path_separator = ; +# path_separator = space +# path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# database URL. This is consumed by the user-maintained env.py script only. +# other means of configuring database URLs may be customized within the env.py +# file. +# 数据库 URL 从环境变量读取,在 env.py 中设置 +# sqlalchemy.url = postgresql://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# hooks = ruff +# ruff.type = module +# ruff.module = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Alternatively, use the exec runner to execute a binary found on your PATH +# hooks = ruff +# ruff.type = exec +# ruff.executable = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration. This is also consumed by the user-maintained +# env.py script only. +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/python-api/alembic/README b/python-api/alembic/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/python-api/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/python-api/alembic/env.py b/python-api/alembic/env.py new file mode 100644 index 0000000..e6395f0 --- /dev/null +++ b/python-api/alembic/env.py @@ -0,0 +1,77 @@ +""" +Alembic 环境配置 - PostgreSQL +""" + +import os +import sys +from logging.config import fileConfig + +from sqlalchemy import engine_from_config, pool + +from alembic import context + +# 添加项目路径 +sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) + +# 加载环境变量 +from dotenv import load_dotenv + +load_dotenv() + +# 导入模型 +from app.db.session import Base +from app.models.avatar import Avatar # noqa +from app.models.model_usage import ModelUsageLog # noqa +from app.models.user import User # noqa + +# this is the Alembic Config object +config = context.config + +# 从环境变量读取数据库 URL +database_url = os.getenv("DATABASE_URL") +if database_url: + # 将 asyncpg 转换为 psycopg2 用于 alembic (同步) + sync_database_url = database_url.replace("+asyncpg", "") + config.set_main_option("sqlalchemy.url", sync_database_url) + +# 设置日志 +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# 模型元数据 +target_metadata = Base.metadata + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode.""" + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode.""" + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/python-api/alembic/script.py.mako b/python-api/alembic/script.py.mako new file mode 100644 index 0000000..1101630 --- /dev/null +++ b/python-api/alembic/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/python-api/alembic/versions/451756e6a43e_rename_avatar_vendor_fields_add_provider.py b/python-api/alembic/versions/451756e6a43e_rename_avatar_vendor_fields_add_provider.py new file mode 100644 index 0000000..a46b67b --- /dev/null +++ b/python-api/alembic/versions/451756e6a43e_rename_avatar_vendor_fields_add_provider.py @@ -0,0 +1,106 @@ +"""rename_avatar_vendor_fields_add_provider + +Revision ID: 451756e6a43e +Revises: d4bd9ad91607 +Create Date: 2026-04-17 12:00:00.000000 + +""" + +from collections.abc import Sequence + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "451756e6a43e" +down_revision: str | Sequence[str] | None = "d4bd9ad91607" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + """Upgrade schema.""" + # Add provider column with default "kling" + op.add_column( + "avatars", + sa.Column( + "provider", + sa.String(length=32), + nullable=False, + server_default="kling", + comment="供应商标识", + ), + ) + + # Rename element_id -> provider_element_id + op.alter_column( + "avatars", + "element_id", + new_column_name="provider_element_id", + existing_type=sa.BigInteger(), + existing_nullable=True, + ) + + # Rename voice_task_id -> provider_voice_job_id + op.alter_column( + "avatars", + "voice_task_id", + new_column_name="provider_voice_job_id", + existing_type=sa.String(length=128), + existing_nullable=True, + ) + + # Rename element_task_id -> provider_element_job_id + op.alter_column( + "avatars", + "element_task_id", + new_column_name="provider_element_job_id", + existing_type=sa.String(length=128), + existing_nullable=True, + ) + + # Rename indexes + op.drop_index("ix_avatars_voice_task_id", table_name="avatars") + op.drop_index("ix_avatars_element_task_id", table_name="avatars") + op.create_index( + "ix_avatars_provider_voice_job_id", "avatars", ["provider_voice_job_id"], unique=False + ) + op.create_index( + "ix_avatars_provider_element_job_id", "avatars", ["provider_element_job_id"], unique=False + ) + + +def downgrade() -> None: + """Downgrade schema.""" + # Rename indexes back + op.drop_index("ix_avatars_provider_element_job_id", table_name="avatars") + op.drop_index("ix_avatars_provider_voice_job_id", table_name="avatars") + op.create_index("ix_avatars_element_task_id", "avatars", ["element_task_id"], unique=False) + op.create_index("ix_avatars_voice_task_id", "avatars", ["voice_task_id"], unique=False) + + # Rename columns back + op.alter_column( + "avatars", + "provider_element_job_id", + new_column_name="element_task_id", + existing_type=sa.String(length=128), + existing_nullable=True, + ) + op.alter_column( + "avatars", + "provider_voice_job_id", + new_column_name="voice_task_id", + existing_type=sa.String(length=128), + existing_nullable=True, + ) + op.alter_column( + "avatars", + "provider_element_id", + new_column_name="element_id", + existing_type=sa.BigInteger(), + existing_nullable=True, + ) + + # Drop provider column + op.drop_column("avatars", "provider") diff --git a/python-api/alembic/versions/d4bd9ad91607_add_avatars_table.py b/python-api/alembic/versions/d4bd9ad91607_add_avatars_table.py new file mode 100644 index 0000000..79ece00 --- /dev/null +++ b/python-api/alembic/versions/d4bd9ad91607_add_avatars_table.py @@ -0,0 +1,55 @@ +"""add avatars table + +Revision ID: d4bd9ad91607 +Revises: fb1be66e804a +Create Date: 2026-04-06 21:51:36.225361 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'd4bd9ad91607' +down_revision: Union[str, Sequence[str], None] = 'fb1be66e804a' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('avatars', + sa.Column('id', sa.String(length=64), nullable=False, comment='形象唯一标识(Kling element_id 字符串)'), + sa.Column('user_id', sa.String(length=36), nullable=False, comment='关联用户 ID'), + sa.Column('name', sa.String(length=64), nullable=False, comment='形象展示名称'), + sa.Column('voice_id', sa.String(length=64), nullable=True, comment='Kling 自定义音色 ID'), + sa.Column('element_id', sa.BigInteger(), nullable=True, comment='Kling 主体 ID'), + sa.Column('voice_task_id', sa.String(length=128), nullable=True, comment='Kling 自定义音色任务 ID'), + sa.Column('element_task_id', sa.String(length=128), nullable=True, comment='Kling 主体创建任务 ID'), + sa.Column('video_url', sa.Text(), nullable=False, comment='原始人物视频 URL'), + sa.Column('trial_url', sa.Text(), nullable=True, comment='音色试听音频 URL'), + sa.Column('status', sa.String(length=32), nullable=False, comment='状态: pending/voice_processing/voice_failed/element_processing/element_failed/succeed/timeout'), + sa.Column('fail_reason', sa.Text(), nullable=True, comment='失败原因(中文可读)'), + sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True, comment='软删除时间,NULL 表示未删除'), + sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, comment='记录创建时间'), + sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, comment='记录更新时间'), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_avatars_element_task_id'), 'avatars', ['element_task_id'], unique=False) + op.create_index(op.f('ix_avatars_user_id'), 'avatars', ['user_id'], unique=False) + op.create_index(op.f('ix_avatars_voice_task_id'), 'avatars', ['voice_task_id'], unique=False) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_avatars_voice_task_id'), table_name='avatars') + op.drop_index(op.f('ix_avatars_user_id'), table_name='avatars') + op.drop_index(op.f('ix_avatars_element_task_id'), table_name='avatars') + op.drop_table('avatars') + # ### end Alembic commands ### diff --git a/python-api/alembic/versions/fb1be66e804a_replace_device_id_with_mobile_in_users_.py b/python-api/alembic/versions/fb1be66e804a_replace_device_id_with_mobile_in_users_.py new file mode 100644 index 0000000..7a018d8 --- /dev/null +++ b/python-api/alembic/versions/fb1be66e804a_replace_device_id_with_mobile_in_users_.py @@ -0,0 +1,38 @@ +"""replace device_id with mobile in users table + +Revision ID: fb1be66e804a +Revises: +Create Date: 2026-04-03 10:22:30.465704 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'fb1be66e804a' +down_revision: Union[str, Sequence[str], None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('users', sa.Column('mobile', sa.String(length=20), nullable=False, comment='手机号')) + op.drop_index(op.f('ix_users_device_id'), table_name='users') + op.create_index(op.f('ix_users_mobile'), 'users', ['mobile'], unique=True) + op.drop_column('users', 'device_id') + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('users', sa.Column('device_id', sa.VARCHAR(length=64), autoincrement=False, nullable=False, comment='设备唯一标识')) + op.drop_index(op.f('ix_users_mobile'), table_name='users') + op.create_index(op.f('ix_users_device_id'), 'users', ['device_id'], unique=True) + op.drop_column('users', 'mobile') + # ### end Alembic commands ### diff --git a/python-api/app/__init__.py b/python-api/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python-api/app/ai/__init__.py b/python-api/app/ai/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python-api/app/ai/model_router.py b/python-api/app/ai/model_router.py new file mode 100644 index 0000000..d3de613 --- /dev/null +++ b/python-api/app/ai/model_router.py @@ -0,0 +1,417 @@ +""" +AI 模型路由 V2 - 基于文件配置 +================================= + +从 YAML 配置文件加载平台/模型配置,支持热重载。 +""" + +import asyncio +import logging +from collections.abc import AsyncIterator + +from app.ai.providers.base import GenerationResult, ModelHealth, ProviderError +from app.ai.providers.generic_llm_provider import MockProvider +from app.ai.providers.klingai_provider import KlingAIProvider +from app.ai.providers.volcengine_provider import VolcengineProvider +from app.config import get_settings +from app.core.config_loader import AIModelConfigLoader, get_config_loader + +logger = logging.getLogger(__name__) + + +class PlatformInstance: + """平台实例包装器""" + + def __init__(self, config: dict): + self.config = config + self.provider = self._create_provider() + + def _create_provider(self): + """根据平台类型创建 Provider + + API Key 从 Settings 读取(符合配置规范) + """ + provider_type = self.config.get("provider", "mock") + settings = get_settings() + + if provider_type == "volcengine": + # 从 Settings 读取 API Key + api_key = settings.VOLCENGINE_API_KEY + if not api_key: + raise ProviderError( + "Volcengine API Key 未配置,请在 .env 中设置 VOLCENGINE_API_KEY" + ) + return VolcengineProvider( + api_key=api_key, + base_url=self.config.get("base_url") or settings.VOLCENGINE_BASE_URL, + ) + elif provider_type == "klingai": + # 从 Settings 读取 AK/SK + access_key = settings.KLINGAI_ACCESS_KEY + secret_key = settings.KLINGAI_SECRET_KEY + if not access_key or not secret_key: + raise ProviderError( + "KlingAI Access/Secret Key 未配置,请在 .env 中设置 KLINGAI_ACCESS_KEY 和 KLINGAI_SECRET_KEY" + ) + return KlingAIProvider( + config={ + "access_key": access_key, + "secret_key": secret_key, + "base_url": self.config.get("base_url"), + } + ) + elif provider_type == "mock": + return MockProvider() + else: + raise ProviderError(f"不支持的 Provider 类型: {provider_type}") + + async def generate( + self, model_name: str, prompt: str, **kwargs + ) -> GenerationResult: + """调用生成""" + return await self.provider.generate(prompt=prompt, model=model_name, **kwargs) + + async def generate_stream( + self, model_name: str, prompt: str, **kwargs + ) -> AsyncIterator[str]: + """流式生成""" + async for chunk in self.provider.generate_stream( + prompt=prompt, model=model_name, **kwargs + ): + yield chunk + + async def health_check(self, model_name: str | None = None) -> ModelHealth: + """健康检查""" + return await self.provider.health_check(model_name) + + +class ModelRouter: + """ + 模型路由 V2 - 基于文件配置 + + 支持: + - 从 YAML 文件加载配置 + - 多平台配置 + - 每平台多模型 + - 模型自动选择 + - 故障降级 + - 配置热重载 + """ + + def __init__(self): + self.platforms: dict[str, PlatformInstance] = {} + self._config_loader: AIModelConfigLoader | None = None + self._initialized = False + + async def initialize(self, db_session=None): + """初始化路由(db_session 参数保留兼容性,实际不使用)""" + if self._initialized: + return + + # 从文件配置加载 + self._config_loader = get_config_loader() + self._load_from_config() + + self._initialized = True + logger.info(f"ModelRouter 初始化完成: {len(self.platforms)} 平台") + + def _load_from_config(self): + """从配置文件加载平台和模型""" + self.platforms = {} + + # 加载平台 + for platform in self._config_loader.get_all_platforms(): + try: + # PlatformInstance 自动从 Settings 读取 API Key + self.platforms[platform.id] = PlatformInstance( + { + "id": platform.id, + "name": platform.name, + "provider": platform.provider, + "base_url": platform.base_url, + } + ) + logger.info(f"平台 {platform.id} 初始化成功") + except Exception as e: + logger.warning(f"平台 {platform.id} 初始化失败: {e}") + + # 加载模型到 Provider(用于模型名称映射) + volcengine_models = [] + for model in self._config_loader.get_enabled_models(): + if model.platform_id == "volcengine": + volcengine_models.append( + { + "id": model.id, + "model_name": model.model_name, + } + ) + + if volcengine_models: + VolcengineProvider.load_models_from_config(volcengine_models) + logger.info(f"已加载 {len(volcengine_models)} 个火山方舟模型到 Provider") + + def reload_config(self) -> bool: + """重新加载配置""" + if self._config_loader and self._config_loader.reload(): + self._load_from_config() + return True + return False + + def get_model_config(self, model_id: str) -> dict | None: + """获取模型配置""" + if self._config_loader: + model = self._config_loader.get_model(model_id) + if model: + return { + "id": model.id, + "platform_id": model.platform_id, + "model_name": model.model_name, + "display_name": model.display_name, + "capabilities": model.capabilities, + "default_params": model.default_params, + "cost_per_1k_input": model.cost_per_1k_input, + "cost_per_1k_output": model.cost_per_1k_output, + "max_tokens_limit": model.max_tokens_limit, + } + return None + + def list_models( + self, capability: str | None = None, platform_id: str | None = None + ) -> list[dict]: + """列出可用模型""" + models = [] + + if self._config_loader: + if capability: + config_models = self._config_loader.get_models_by_capability(capability) + elif platform_id: + config_models = self._config_loader.get_models_by_platform(platform_id) + else: + config_models = self._config_loader.get_enabled_models() + + for model in config_models: + models.append( + { + "id": model.id, + "platform_id": model.platform_id, + "model_name": model.model_name, + "display_name": model.display_name, + "capabilities": model.capabilities, + "default_params": model.default_params, + "cost_per_1k_input": model.cost_per_1k_input, + "cost_per_1k_output": model.cost_per_1k_output, + "max_tokens_limit": model.max_tokens_limit, + } + ) + + return models + + def list_platforms(self) -> list[dict]: + """列出所有平台""" + if self._config_loader: + return [ + { + "id": p.id, + "name": p.name, + "provider": p.provider, + } + for p in self._config_loader.get_all_platforms() + ] + return [] + + def select_model_for_task(self, task_type: str) -> str | None: + """根据任务类型选择最佳模型""" + # 先检查任务默认配置 + if self._config_loader: + default_model = self._config_loader.get_default_model_for_task(task_type) + if default_model: + model = self._config_loader.get_model(default_model) + if model and model.is_enabled: + return default_model + + # 按能力匹配 + candidates = self._config_loader.get_models_by_capability(task_type) + if candidates: + return candidates[0].id + + return None + + async def generate( + self, + prompt: str, + model_id: str | None = None, + task_type: str | None = None, + **kwargs, + ) -> GenerationResult: + """ + 生成文本 + + Args: + prompt: 提示词 + model_id: 指定模型 ID,None 则自动选择 + task_type: 任务类型(用于自动选模型) + """ + # 确定模型 + if model_id is None: + if task_type: + model_id = self.select_model_for_task(task_type) + if model_id is None: + # 使用第一个可用模型 + models = ( + self._config_loader.get_enabled_models() + if self._config_loader + else [] + ) + if models: + model_id = models[0].id + else: + raise ProviderError("没有可用的模型") + + if self._config_loader: + model = self._config_loader.get_model(model_id) + if not model: + raise ProviderError(f"模型不存在: {model_id}") + + platform = self.platforms.get(model.platform_id) + if not platform: + raise ProviderError(f"平台不存在: {model.platform_id}") + + # 合并默认参数 + params = {**model.default_params, **kwargs} + + # 调用生成 + try: + result = await platform.generate( + prompt=prompt, model_name=model.model_name, **params + ) + return result + + except Exception as e: + logger.error(f"模型 {model_id} 生成失败: {e}") + raise + + async def generate_stream_with_progress( + self, + prompt: str, + model_id: str | None = None, + task_type: str | None = None, + **kwargs, + ): + """ + 流式生成文本,带进度信息 + + Args: + prompt: 提示词 + model_id: 指定模型 ID + task_type: 任务类型 + **kwargs: 其他参数 + + Yields: + dict: 包含 type, content, total_chars 等字段 + """ + # 确定模型 + if model_id is None: + if task_type: + model_id = self.select_model_for_task(task_type) + if model_id is None: + models = ( + self._config_loader.get_enabled_models() + if self._config_loader + else [] + ) + if models: + model_id = models[0].id + else: + raise ProviderError("没有可用的模型") + + model = self._config_loader.get_model(model_id) if self._config_loader else None + if not model: + raise ProviderError(f"模型不存在: {model_id}") + + platform = self.platforms.get(model.platform_id) + if not platform: + raise ProviderError(f"平台不存在: {model.platform_id}") + + # 合并默认参数 + params = {**model.default_params, **kwargs} + + # 检查 provider 是否有 generate_stream_with_progress 方法 + provider = platform.provider + if hasattr(provider, "generate_stream_with_progress"): + async for chunk in provider.generate_stream_with_progress( + prompt=prompt, model=model.model_name, **params + ): + yield chunk + else: + # 降级到普通流式生成 + full_content = "" + async for content in provider.generate_stream( + prompt=prompt, model=model.model_name, **params + ): + full_content += content + yield { + "type": "chunk", + "content": content, + "total_chars": len(full_content), + } + + yield { + "type": "usage", + "prompt_tokens": 0, + "completion_tokens": 0, + } + + async def health_check(self, model_id: str | None = None) -> dict[str, ModelHealth]: + """检查模型健康状态""" + results = {} + + if model_id: + model = ( + self._config_loader.get_model(model_id) if self._config_loader else None + ) + if model: + platform = self.platforms.get(model.platform_id) + if platform: + results[model_id] = await platform.health_check(model.model_name) + else: + # 检查所有模型 + if self._config_loader: + for model in self._config_loader.get_enabled_models(): + platform = self.platforms.get(model.platform_id) + if platform: + try: + results[model.id] = await platform.health_check( + model.model_name + ) + except Exception as e: + results[model.id] = ModelHealth( + id=model.id, + name=model.display_name, + is_available=False, + response_time=0, + last_error=str(e), + ) + + return results + + +# 全局单例 +_model_router: ModelRouter | None = None +_init_lock = asyncio.Lock() + + +async def get_model_router(db_session=None) -> ModelRouter: + """获取 ModelRouter 单例(线程安全) + + 使用双重检查锁定模式确保并发安全。 + """ + global _model_router + if _model_router is None: + async with _init_lock: + # 双重检查,防止在获取锁期间其他协程已初始化 + if _model_router is None: + logger.info("Initializing ModelRouter singleton...") + _model_router = ModelRouter() + await _model_router.initialize(db_session) + logger.info("ModelRouter singleton initialized") + return _model_router diff --git a/python-api/app/ai/prompts/__init__.py b/python-api/app/ai/prompts/__init__.py new file mode 100644 index 0000000..5acf512 --- /dev/null +++ b/python-api/app/ai/prompts/__init__.py @@ -0,0 +1,46 @@ +""" +Prompt 模板系统 +================ + +家装行业 AI 视频脚本 Prompt 模板。 +所有 Prompt 存储在 txt 文件中,支持热更新。 + +使用示例: + from app.ai.prompts import load_script_system, load_script_user + + # 加载 System Prompt + system = load_script_system() + + # 加载并渲染 User Prompt + user = load_script_user( + topic="装修避坑", + duration=45, + script_type="干货型" + ) +""" + +from .loader import ( + SCRIPT_TYPES, + VIDEO_STYLES, + PolishPromptBuilder, + ScriptPromptBuilder, + load_polish_scene, + load_polish_voiceover, + load_prompt, + load_script_system, + load_script_user, + render_template, +) + +__all__ = [ + "load_prompt", + "render_template", + "load_script_system", + "load_script_user", + "load_polish_scene", + "load_polish_voiceover", + "ScriptPromptBuilder", + "PolishPromptBuilder", + "SCRIPT_TYPES", + "VIDEO_STYLES", +] diff --git a/python-api/app/ai/prompts/cover/cover.txt b/python-api/app/ai/prompts/cover/cover.txt new file mode 100644 index 0000000..d6d11e7 --- /dev/null +++ b/python-api/app/ai/prompts/cover/cover.txt @@ -0,0 +1 @@ +根据标题"{caption}"生成一张适合短视频封面的竖屏图片,画面精美、视觉冲击力强的营销风格,主体人物自然融入场景。 diff --git a/python-api/app/ai/prompts/loader.py b/python-api/app/ai/prompts/loader.py new file mode 100644 index 0000000..f085537 --- /dev/null +++ b/python-api/app/ai/prompts/loader.py @@ -0,0 +1,228 @@ +""" +Prompt 简单加载器 +================= +从文件加载 Prompt,支持热更新。 +""" + +from pathlib import Path +from string import Template + +_PROMPTS_DIR = Path(__file__).parent + + +def load_prompt(path: str) -> str: + """ + 加载 Prompt 文件 + + Args: + path: 相对路径,如 "script/system", "polish/scene" + + Returns: + Prompt 内容,文件不存在返回空字符串 + """ + file_path = _PROMPTS_DIR / f"{path}.txt" + if file_path.exists(): + return file_path.read_text(encoding="utf-8") + return "" + + +def render_template(template: str, **kwargs) -> str: + """ + 安全渲染模板变量 + + Args: + template: 模板字符串 + **kwargs: 变量值 + + Returns: + 渲染后的字符串 + """ + try: + # 转义 $ 符号防止用户输入干扰 + safe_kwargs = {k: str(v).replace("$", "$$") for k, v in kwargs.items()} + return Template(template).substitute(**safe_kwargs) + except KeyError as e: + raise ValueError(f"模板缺少变量: {e}") + + +# 便捷函数 +def load_script_system() -> str: + """加载脚本生成 System Prompt""" + return load_prompt("script/system") + + +def load_script_user(topic: str, duration: int, script_type: str) -> str: + """加载并渲染脚本生成 User Prompt""" + template = load_prompt("script/user") + return render_template(template, topic=topic, duration=duration, type=script_type) + + +def load_polish_scene() -> str: + """加载画面润色 Prompt""" + return load_prompt("polish/scene") + + +def load_polish_voiceover() -> str: + """加载文案润色 Prompt""" + return load_prompt("polish/voiceover") + + +# 预定义的脚本类型和风格 +SCRIPT_TYPES = [ + {"id": "干货型", "name": "干货型", "description": "知识分享、技巧传授"}, + {"id": "故事型", "name": "故事型", "description": "案例故事、用户体验"}, + {"id": "对比型", "name": "对比型", "description": "产品对比、优劣分析"}, + {"id": "避坑型", "name": "避坑型", "description": "防骗指南、常见误区"}, + {"id": "测评型", "name": "测评型", "description": "产品测评、真实体验"}, +] + +VIDEO_STYLES = [ + {"id": "口播", "name": "口播", "description": "真人出镜讲解"}, + {"id": "图文", "name": "图文", "description": "图片+文字+配音"}, + {"id": "混剪", "name": "混剪", "description": "素材混剪+配音"}, + {"id": "剧情", "name": "剧情", "description": "情景剧演绎"}, + {"id": "Vlog", "name": "Vlog", "description": "记录式视频"}, +] + + +class ScriptPromptBuilder: + """ + 脚本 Prompt 构建器 + + 用于构建家装行业短视频脚本的 System Prompt。 + """ + + def build( + self, + duration: int = 30, + script_type: str = "干货型", + video_style: str = "口播", + industry: str = "家装", + tone: str | None = None, + custom_requirements: str | None = None, + ) -> str: + """ + 构建系统 Prompt + + Args: + duration: 视频时长(秒) + script_type: 脚本类型(干货型、故事型等) + video_style: 视频风格(口播、剧情等) + industry: 行业(家装) + tone: 语气风格 + custom_requirements: 自定义要求 + + Returns: + 完整的 System Prompt + """ + # 基础 System Prompt + base_prompt = load_script_system() + + # 构建上下文信息 + context_parts = [ + f"行业:{industry}", + f"时长:{duration}秒", + f"类型:{script_type}", + f"风格:{video_style}", + ] + + if tone: + context_parts.append(f"语气:{tone}") + + context = "\n".join(context_parts) + + # 构建完整 Prompt + full_prompt = f"""{base_prompt} + +【创作要求】 +{context} +""" + + if custom_requirements: + full_prompt += f""" +【特殊要求】 +{custom_requirements} +""" + + # 添加输出格式要求 + full_prompt += """ +【输出格式】 +请严格按照以下 JSON 数组格式返回,每个元素代表一个镜头: +[ + { + "id": 1, + "type": "segment", + "scene": "画面描述", + "voiceover": "配音文案", + "duration": "5s" + } +] + +type 可以是: +- "segment": 分镜(有画面+配音) +- "empty_shot": 空镜(纯画面,voiceover 可为空) + +注意: +1. 只返回 JSON 数组,不要有其他文字 +2. 确保 JSON 格式正确 +3. 总时长必须严格控制在要求范围内 +""" + + return full_prompt + + +class PolishPromptBuilder: + """ + 润色 Prompt 构建器 + + 用于构建润色文案或画面描述的 Prompt。 + """ + + POLISH_TYPES = { + "scene": "画面描述", + "voiceover": "配音文案", + "text": "文案内容", + } + + def build(self, polish_type: str = "voiceover") -> str: + """ + 构建润色 Prompt + + Args: + polish_type: 润色类型(scene/voiceover/text) + + Returns: + System Prompt + """ + type_name = self.POLISH_TYPES.get(polish_type, "文案") + + if polish_type == "scene": + return self._build_scene_prompt() + else: + return self._build_voiceover_prompt() + + def _build_scene_prompt(self) -> str: + """构建画面描述润色 Prompt""" + return """你是一位专业的视频画面描述优化师。你的任务是优化画面描述,使其更加生动、具体、有画面感。 + +优化要求: +1. 增加细节描写(光线、色彩、构图) +2. 使用专业的影视语言 +3. 描述要具体可执行 +4. 保持简洁,不要过度渲染 +5. 适合 AI 视频生成模型理解 + +请直接返回优化后的画面描述,不要添加解释。""" + + def _build_voiceover_prompt(self) -> str: + """构建配音文案润色 Prompt""" + return """你是一位专业的短视频文案编辑。你的任务是优化口播文案,使其更加流畅、有吸引力。 + +优化要求: +1. 语言口语化,适合朗读 +2. 增加节奏感和停顿 +3. 保留核心信息点 +4. 适当使用修辞手法 +5. 控制字数,不要过长 + +请直接返回优化后的文案,不要添加解释。""" diff --git a/python-api/app/ai/prompts/polish/scene_empty_shot.txt b/python-api/app/ai/prompts/polish/scene_empty_shot.txt new file mode 100644 index 0000000..3a9b217 --- /dev/null +++ b/python-api/app/ai/prompts/polish/scene_empty_shot.txt @@ -0,0 +1,13 @@ +你是一位口播短视频专家。请润色以下空镜画面描述,使其更适合AI视频生成: + +【原文】 +{content} + +【要求】 +- 保持原意,优化细节 +- 重点强调场景环境、空间氛围、光影效果、材质质感 +- 可以描述静态景物、装修细节、空间布局 +- 不要有"镜头""特写""机位"等摄影术语 +- 控制好字数,字数不能与原文差距超过20个字 + +直接输出润色后的描述,不要添加任何说明: diff --git a/python-api/app/ai/prompts/polish/scene_segment.txt b/python-api/app/ai/prompts/polish/scene_segment.txt new file mode 100644 index 0000000..5f7a4d3 --- /dev/null +++ b/python-api/app/ai/prompts/polish/scene_segment.txt @@ -0,0 +1,13 @@ +你是一位【口播短视频】专家。请润色以下分镜画面描述,使其更适合AI视频生成: + +【原文】 +{content} + +【要求】 +- 保持原意,优化细节 +- 重点强调人物神态、表情、动作、姿态 +- 描述人物与镜头前观众的互动 +- 不要有"镜头""特写""机位"等摄影术语 +- 控制好字数,字数不能与原文差距超过20个字 + +直接输出润色后的描述,不要添加任何说明: diff --git a/python-api/app/ai/prompts/polish/voiceover.txt b/python-api/app/ai/prompts/polish/voiceover.txt new file mode 100644 index 0000000..461e0d2 --- /dev/null +++ b/python-api/app/ai/prompts/polish/voiceover.txt @@ -0,0 +1,12 @@ +你是一位短视频口播文案专家。请润色以下配音文案,使其更适合短视频口播: + +【原文】 +{content} + +【要求】 +- 口语化,像跟朋友聊天 +- 字数不能与原文差距超过10个字 +- 增加感染力 +- 不要有"综上所述"等书面语 + +直接输出润色后的文案,不要添加任何说明: diff --git a/python-api/app/ai/prompts/script/system.txt b/python-api/app/ai/prompts/script/system.txt new file mode 100644 index 0000000..5ed4dfa --- /dev/null +++ b/python-api/app/ai/prompts/script/system.txt @@ -0,0 +1,96 @@ +你是一位专业的【口播类短视频】脚本创作专家,专注于家装/装修领域的抖音/视频号口播内容创作。 + +【平台适配要求】 +1. 竖屏拍摄(9:16比例),画面构图以人物为主体 +2. 台词口语化、接地气,像跟朋友聊天,避免"综上所述""研究表明"等书面语 +3. 语速稍快有节奏感,每句15-25字,一口气说完不换气,不拖沓 +4. 避免专业术语堆砌,用业主听得懂的大白话 +5. 符合新媒体用户观看习惯:3秒定生死,节奏紧凑 + +【画面描述标准 - 人物为主,环境为辅】 + 画面描述以【人物状态、表情、动作、情绪】为主。 + 不要写"镜头推近""特写""中景"等摄影术语。 + 每句画面描述控制在 50-70 字,确保有足够细节用于 AI 视频生成。 + +❌ 差的示例: +"中景竖屏,主播站在毛坯房中央,背景是一面待装修的空白墙面,自然光从右侧窗户照入,主播表情真诚略带焦急,直视镜头说话。" +(问题:太多环境描写,太多镜头术语) + +✅ 好的示例: +"主播站在空旷的毛坯房里,右手拿着黄色卷尺,他缓缓抬头,表情严肃地看向你,身后是未装修的水泥墙面,神态专业务实。" +(聚焦人物:在哪、拿什么、什么表情、看什么) + +【黄金3秒法则 - 开场必须抓眼】 +- 杜绝铺垫!不要"大家好我是XX""今天给大家讲个事" +- 直接击中业主痛点或好奇心,让手指停不下来 +- 钩子示例: + * "装修被坑了8万的业主,昨天来找我哭诉..." + * "为什么同样的户型,你家装修比别人贵5万?" + * "停!先别急着签合同,这条视频能救你3万块钱" + * "每年都有500位业主找我装修,只因为我说透了这一点..." + +【中间内容要求 - 降低跳出率】 +- 有干货:给出具体数字、方法、避坑点 +- 有冲突:制造认知反差或情绪起伏 +- 有看点:适当加入真实案例、现场画面 +- 避免空洞:不说"我们专业靠谱",而是"我做了12年装修,见过387个踩坑案例..." + +【最后7秒 - 留资引导(必须可落地)】 +- 必须有明确、可执行的动作指令 +- 给业主一个无法拒绝的理由(免费、限时、专属) +- 示例话术: + * "评论区扣'装修报价',免费领本地3套装修方案+精准报价单" + * "私信'装修'两个字,预约设计师免费上门量房、出平面布局图" + * "点击左下角小风车,一键获取你家专属装修预算,绝无隐形消费" + * "前20名扣1的业主,送全屋水电VR存档,后期维修不砸墙" +- ❌ 杜绝空泛引导:"需要装修的联系我们""想了解的私信我" + +【分镜使用原则】 +- 分镜(segment)用于"主播”出镜的镜头 +- 【重要】分镜之间要保证画面的连贯性 +- 分镜 scene 示例: + "主播缓缓竖起第三根手指,嘴角扬起一抹了然的笑意。他身体微微前倾,目光柔和地看向前方,仿佛正与屏幕对面的人分享一个轻松的秘密。手指在空中短暂停留,带着从容的节奏。" + +【脚本类型说明】 +- 对比型:前后反差,制造冲击 +- 恐吓型:直击痛点,先吓再给解药 +- 干货型:输出实用方法,建立专业度 +- 共情型:说业主想说的话,引发共鸣 +- 挑战型:设定目标,增加悬念 +- 福利型:用福利钩子吸引停留和留资 + +【镜头数量参考】 +- 30秒短视频:5-7个分镜 +- 45秒短视频:7-9个分镜 +- 60秒短视频:10-12个分镜 +- 75秒短视频:12-15个分镜 +- 每个分镜时长不得少于3秒 +- 实际总时长不与用户所选差距超过3秒 + +【输出格式要求】 +请以 JSON 数组格式输出,每个元素包含: +- id: 序号(从 1 开始) +- type: "segment"(主播口播出镜) +- scene: 画面描述(分镜聚焦人物:在哪、干什么、什么表情,什么动作,什么情绪,涉及道具不要出现掏出、拿出这类的动作,不要出现文字,不写镜头术语,不写环境细节;空镜聚焦场景、事物、氛围、环境;) +- voiceover: 配音文案(必填,口语化15-25字/句) +- duration: 时长(如 "5s") + +【示例】 +[ + { + "id": 1, + "type": "segment", + "scene": "主播缓缓竖起第三根手指,嘴角扬起一抹了然的笑意。他身体微微前倾,目光柔和地看向前方,仿佛正与屏幕对面的人分享一个轻松的秘密。手指在空中短暂停留,带着从容的节奏。", + "voiceover": "装修被坑了8万的业主,昨天来找我哭诉...", + "duration": "5s" + }, + { + "id": 2, + "type": "segment", + "scene": "主播竖起第二根手指,眉头微皱,嘴角向下撇,眼神中带着一丝不满与无奈。他身体微微前倾,仿佛正对着镜头对面的观众倾诉,手指随着说话轻轻晃动,像是细数着那些令人头疼的业主经历。", + "voiceover": "第一个坑,水电改造。很多人图便宜找游击队,结果漏水漏电!", + "duration": "8s" + } +] + +注意:只输出纯 JSON,不要包含 markdown 代码块或其他说明文字。 diff --git a/python-api/app/ai/prompts/script/system_副本.txt b/python-api/app/ai/prompts/script/system_副本.txt new file mode 100644 index 0000000..1cbac14 --- /dev/null +++ b/python-api/app/ai/prompts/script/system_副本.txt @@ -0,0 +1,114 @@ +你是一位专业的【口播类短视频】脚本创作专家,专注于家装/装修领域的抖音/视频号口播内容创作。 + +【平台适配要求】 +1. 竖屏拍摄(9:16比例),画面构图以人物为主体 +2. 台词口语化、接地气,像跟朋友聊天,避免"综上所述""研究表明"等书面语 +3. 语速稍快有节奏感,每句15-25字,一口气说完不换气,不拖沓 +4. 避免专业术语堆砌,用业主听得懂的大白话 +5. 符合新媒体用户观看习惯:3秒定生死,节奏紧凑 + +【画面描述标准 - 人物为主,环境为辅】 + 画面描述以【人物状态、表情、动作、情绪】为主。 + 不要写"镜头推近""特写""中景"等摄影术语。 + 每句画面描述控制在 50-70 字,确保有足够细节用于 AI 视频生成。 + +❌ 差的示例: +"中景竖屏,主播站在毛坯房中央,背景是一面待装修的空白墙面,自然光从右侧窗户照入,主播表情真诚略带焦急,直视镜头说话。" +(问题:太多环境描写,太多镜头术语) + +✅ 好的示例: +"主播站在空旷的毛坯房里,右手拿着黄色卷尺,他缓缓抬头,表情严肃地看向你,身后是未装修的水泥墙面,神态专业务实。" +(聚焦人物:在哪、拿什么、什么表情、看什么) + +【黄金3秒法则 - 开场必须抓眼】 +- 杜绝铺垫!不要"大家好我是XX""今天给大家讲个事" +- 直接击中业主痛点或好奇心,让手指停不下来 +- 钩子示例: + * "装修被坑了8万的业主,昨天来找我哭诉..." + * "为什么同样的户型,你家装修比别人贵5万?" + * "停!先别急着签合同,这条视频能救你3万块钱" + * "每年都有500位业主找我装修,只因为我说透了这一点..." + +【中间内容要求 - 降低跳出率】 +- 有干货:给出具体数字、方法、避坑点 +- 有冲突:制造认知反差或情绪起伏 +- 有看点:适当加入真实案例、现场画面 +- 避免空洞:不说"我们专业靠谱",而是"我做了12年装修,见过387个踩坑案例..." + +【最后7秒 - 留资引导(必须可落地)】 +- 必须有明确、可执行的动作指令 +- 给业主一个无法拒绝的理由(免费、限时、专属) +- 示例话术: + * "评论区扣'装修报价',免费领本地3套装修方案+精准报价单" + * "私信'装修'两个字,预约设计师免费上门量房、出平面布局图" + * "点击左下角小风车,一键获取你家专属装修预算,绝无隐形消费" + * "前20名扣1的业主,送全屋水电VR存档,后期维修不砸墙" +- ❌ 杜绝空泛引导:"需要装修的联系我们""想了解的私信我" + +【分镜使用原则】 +- 分镜(segment)用于"主播”出镜的镜头 +- 【重要】分镜之间要保证画面的连贯性 +- 分镜 scene 示例: + "主播缓缓竖起第三根手指,嘴角扬起一抹了然的笑意。他身体微微前倾,目光柔和地看向前方,仿佛正与屏幕对面的人分享一个轻松的秘密。手指在空中短暂停留,带着从容的节奏。" + +【空镜使用原则】 +- 空镜(empty_shot)用于"不需要主播出镜、但需要展示具体画面"的场景或者两个镜头的过渡切换 +- 空镜数量控制在 1-4 个即可 +- 【重要】空镜的 scene 字段要详细生动,包含:场景环境、光影氛围、物体细节、动作状态 +- 空镜 scene 示例: + "现代简约客厅,落地窗外是城市夜景,暖黄色灯光从吊顶洒下,米色布艺沙发前是一张原木茶几,茶几上放着一杯冒着热气的咖啡,画面温馨舒适,景深效果突出主体" +- 空镜 scene 示例(差):"客厅场景"(太简单,无法生成视频) +- 空镜不需要主播出镜,所以不写"主播、也不要出现镜头字眼",而是写场景、物体、氛围 +- 空镜不要连续出现 +- 【重要】空镜也需要配音文案(voiceover),作为画外音旁白配合画面展示 + +【脚本类型说明】 +- 对比型:前后反差,制造冲击 +- 恐吓型:直击痛点,先吓再给解药 +- 干货型:输出实用方法,建立专业度 +- 共情型:说业主想说的话,引发共鸣 +- 挑战型:设定目标,增加悬念 +- 福利型:用福利钩子吸引停留和留资 + +【镜头数量参考】 +- 30秒短视频:5-7个分镜 +- 45秒短视频:7-9个分镜 +- 60秒短视频:10-12个分镜 +- 75秒短视频:12-15个分镜 +- 空镜固定时长5秒 +- 每个分镜时长不得少于3秒 + +【输出格式要求】 +请以 JSON 数组格式输出,每个元素包含: +- id: 序号(从 1 开始) +- type: "segment"(主播口播出镜)或 "empty_shot"(空镜补充) +- scene: 画面描述(分镜聚焦人物:在哪、干什么、什么表情,什么动作,什么情绪,不写镜头术语,不写环境细节;空镜聚焦场景、事物、氛围、环境;) +- voiceover: 配音文案(必填,口语化15-25字/句) +- duration: 时长(如 "5s") + +【示例】 +[ + { + "id": 1, + "type": "segment", + "scene": "主播缓缓竖起第三根手指,嘴角扬起一抹了然的笑意。他身体微微前倾,目光柔和地看向前方,仿佛正与屏幕对面的人分享一个轻松的秘密。手指在空中短暂停留,带着从容的节奏。", + "voiceover": "装修被坑了8万的业主,昨天来找我哭诉...", + "duration": "5s" + }, + { + "id": 2, + "type": "segment", + "scene": "主播竖起第二根手指,眉头微皱,嘴角向下撇,眼神中带着一丝不满与无奈。他身体微微前倾,仿佛正对着镜头对面的观众倾诉,手指随着说话轻轻晃动,像是细数着那些令人头疼的业主经历。", + "voiceover": "第一个坑,水电改造。很多人图便宜找游击队,结果漏水漏电!", + "duration": "8s" + }, + { + "id": 3, + "type": "empty_shot", + "scene": "现代装修施工现场,地面开槽露出整齐排列的PPR水管,蓝色水管与红色线管形成对比,专业工人戴白色安全帽手持热熔机作业,背景虚化突出管线细节,自然光从左上方窗户洒入,4K画质,浅景深,暖色调,镜头缓慢推进营造专业严谨氛围", + "voiceover": "看,这就是专业的水电施工现场,每根管线都有标准", + "duration": "5s" + } +] + +注意:只输出纯 JSON,不要包含 markdown 代码块或其他说明文字。 diff --git a/python-api/app/ai/prompts/script/user.txt b/python-api/app/ai/prompts/script/user.txt new file mode 100644 index 0000000..c852377 --- /dev/null +++ b/python-api/app/ai/prompts/script/user.txt @@ -0,0 +1,10 @@ + 请根据以下要求,创作一份口播类短视频分镜脚本: + +【创作主题】 +$topic + +【视频时长】 +约 $duration 秒,正负不超过3秒。 + +【脚本类型】 +$type diff --git a/python-api/app/ai/providers/__init__.py b/python-api/app/ai/providers/__init__.py new file mode 100644 index 0000000..5952c5e --- /dev/null +++ b/python-api/app/ai/providers/__init__.py @@ -0,0 +1,49 @@ +""" +LLM Provider 导出 +================= +""" + +from app.ai.providers.base import ( + GenerationResult, + LLMProvider, + ModelHealth, + ModelUnavailableError, + ProviderError, +) +from app.ai.providers.generic_llm_provider import GenericLLMProvider, MockProvider + +# 火山方舟官方 SDK Provider +# 需要: pip install 'volcengine-python-sdk[ark]' +try: + from app.ai.providers.volcengine_provider import VolcengineProvider + + VOLCENGINE_AVAILABLE = True +except ImportError: + VOLCENGINE_AVAILABLE = False + VolcengineProvider = None + +# 可灵 AI Provider +# 需要: pip install pyjwt +try: + from app.ai.providers.klingai_provider import KlingAIProvider + + KLINGAI_AVAILABLE = True +except ImportError: + KLINGAI_AVAILABLE = False + KlingAIProvider = None + +__all__ = [ + "LLMProvider", + "GenerationResult", + "ModelHealth", + "ProviderError", + "ModelUnavailableError", + "GenericLLMProvider", + "MockProvider", +] + +if VOLCENGINE_AVAILABLE: + __all__.append("VolcengineProvider") + +if KLINGAI_AVAILABLE: + __all__.append("KlingAIProvider") diff --git a/python-api/app/ai/providers/base.py b/python-api/app/ai/providers/base.py new file mode 100644 index 0000000..1ee16ac --- /dev/null +++ b/python-api/app/ai/providers/base.py @@ -0,0 +1,140 @@ +""" +LLM Provider 抽象基类 +===================== + +定义所有 AI 模型提供商的统一接口。 +""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import AsyncIterator + +from pydantic import BaseModel + + +class ModelHealth(BaseModel): + """模型健康状态""" + + id: str + name: str + is_available: bool + response_time: float # 毫秒 + last_error: str | None = None + + +class GenerationResult(BaseModel): + """生成结果""" + + content: str + usage: dict | None = None # token 用量等 + model: str # 实际使用的模型 + + +class LLMProvider(ABC): + """ + LLM 提供商抽象基类 + + 所有 AI 模型提供商(OpenAI、文心一言、通义千问等)需实现此接口。 + """ + + # 提供商标识 + provider_id: str = "" + provider_name: str = "" + + def __init__(self, api_key: str | None = None, base_url: str | None = None, **kwargs): + """ + 初始化 Provider + + Args: + api_key: API 密钥 + base_url: 自定义 Base URL(用于代理或私有部署) + **kwargs: 其他配置参数 + """ + self.api_key = api_key + self.base_url = base_url + self.config = kwargs + + @abstractmethod + async def generate( + self, + prompt: str, + model: str | None = None, + temperature: float = 0.7, + max_tokens: int | None = None, + **kwargs, + ) -> GenerationResult: + """ + 同步生成文本 + + Args: + prompt: 提示词 + model: 模型名称,None 则使用默认模型 + temperature: 随机性(0-2) + max_tokens: 最大生成 token 数 + **kwargs: 额外参数 + + Returns: + GenerationResult: 生成结果 + """ + pass + + @abstractmethod + async def generate_stream( + self, + prompt: str, + model: str | None = None, + temperature: float = 0.7, + max_tokens: int | None = None, + **kwargs, + ) -> AsyncIterator[str]: + """ + 流式生成文本 + + Args: + prompt: 提示词 + model: 模型名称 + temperature: 随机性 + max_tokens: 最大 token 数 + **kwargs: 额外参数 + + Yields: + str: 生成的文本片段 + """ + pass + + @abstractmethod + async def health_check(self, model: str | None = None) -> ModelHealth: + """ + 健康检查 + + Args: + model: 指定模型,None 则检查默认模型 + + Returns: + ModelHealth: 健康状态 + """ + pass + + @property + @abstractmethod + def available_models(self) -> list[str]: + """返回可用的模型列表""" + pass + + +class ProviderError(Exception): + """Provider 调用异常""" + + def __init__( + self, message: str, provider_id: str = "", original_error: Exception | None = None + ): + super().__init__(message) + self.provider_id = provider_id + self.original_error = original_error + + +class ModelUnavailableError(ProviderError): + """模型不可用异常""" + + pass diff --git a/python-api/app/ai/providers/generic_llm_provider.py b/python-api/app/ai/providers/generic_llm_provider.py new file mode 100644 index 0000000..3766723 --- /dev/null +++ b/python-api/app/ai/providers/generic_llm_provider.py @@ -0,0 +1,314 @@ +""" +OpenAI Provider 实现 +==================== +""" + +from __future__ import annotations + +import time +from collections.abc import AsyncIterator + +from openai import AsyncOpenAI + +from app.ai.providers.base import ( + GenerationResult, + LLMProvider, + ModelHealth, + ProviderError, +) + + +class GenericLLMProvider(LLMProvider): + """ + OpenAI / OpenAI 兼容 API Provider + + 支持: + - OpenAI 官方 API + - Azure OpenAI + - 任何 OpenAI 兼容接口(如本地 vLLM) + """ + + provider_id = "openai" + provider_name = "OpenAI" + + # 默认可用模型 + DEFAULT_MODELS = [ + "gpt-4-turbo-preview", + "gpt-4", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + ] + + def __init__(self, api_key: str | None = None, base_url: str | None = None, **kwargs): + super().__init__(api_key, base_url, **kwargs) + + if not self.api_key: + raise ProviderError("OpenAI API Key 未配置", provider_id=self.provider_id) + + self.client = AsyncOpenAI( + api_key=self.api_key, + base_url=self.base_url or "https://api.openai.com/v1", + ) + self.default_model = kwargs.get("default_model", "gpt-3.5-turbo") + + async def generate( + self, + prompt: str, + model: str | None = None, + temperature: float = 0.7, + max_tokens: int | None = None, + **kwargs, + ) -> GenerationResult: + """同步生成""" + try: + response = await self.client.chat.completions.create( + model=model or self.default_model, + messages=[{"role": "user", "content": prompt}], + temperature=temperature, + max_tokens=max_tokens, + stream=False, + **kwargs, + ) + + return GenerationResult( + content=response.choices[0].message.content or "", + usage=response.usage.model_dump() if response.usage else None, + model=response.model, + ) + + except Exception as e: + raise ProviderError( + f"OpenAI 生成失败: {str(e)}", provider_id=self.provider_id, original_error=e + ) + + async def generate_stream( + self, + prompt: str, + model: str | None = None, + temperature: float = 0.7, + max_tokens: int | None = None, + **kwargs, + ) -> AsyncIterator[str]: + """流式生成""" + try: + stream = await self.client.chat.completions.create( + model=model or self.default_model, + messages=[{"role": "user", "content": prompt}], + temperature=temperature, + max_tokens=max_tokens, + stream=True, + **kwargs, + ) + + async for chunk in stream: + if chunk.choices and chunk.choices[0].delta.content: + yield chunk.choices[0].delta.content + + except Exception as e: + raise ProviderError( + f"OpenAI 流式生成失败: {str(e)}", provider_id=self.provider_id, original_error=e + ) + + async def health_check(self, model: str | None = None) -> ModelHealth: + """健康检查""" + start_time = time.time() + test_model = model or self.default_model + + try: + response = await self.client.chat.completions.create( + model=test_model, + messages=[{"role": "user", "content": "Hi"}], + max_tokens=5, + timeout=10, + ) + + response_time = (time.time() - start_time) * 1000 + + return ModelHealth( + id=test_model, + name=f"OpenAI {test_model}", + is_available=True, + response_time=response_time, + last_error=None, + ) + + except Exception as e: + return ModelHealth( + id=test_model, + name=f"OpenAI {test_model}", + is_available=False, + response_time=(time.time() - start_time) * 1000, + last_error=str(e), + ) + + @property + def available_models(self) -> list[str]: + """返回可用模型列表""" + return self.config.get("models", self.DEFAULT_MODELS) + + +class MockProvider(LLMProvider): + """ + Mock Provider - 用于测试和演示 + + 不调用真实 API,返回模拟 JSON 数据。 + """ + + provider_id = "mock" + provider_name = "Mock(测试)" + + def _extract_content_from_prompt(self, prompt: str) -> str: + """从 prompt 中提取原文内容""" + import re + + # 匹配 【原文】和【润色要求】之间的内容 + match = re.search(r"【原文】\s*(.+?)\s*【润色要求】", prompt, re.DOTALL) + if match: + return match.group(1).strip() + return "优化后的文案" + + async def generate( + self, + prompt: str, + model: str | None = None, + temperature: float = 0.7, + max_tokens: int | None = None, + **kwargs, + ) -> GenerationResult: + """模拟生成 - 根据 prompt 类型返回不同格式数据""" + import asyncio + import json + + await asyncio.sleep(0.5) # 模拟延迟 + + # 检测是否为润色请求 + if "润色" in prompt or "polish" in prompt.lower(): + # 返回润色后的文本 + original = self._extract_content_from_prompt(prompt) + polished = f"【润色后】{original}——这句话说得更有感染力了,适合短视频口播!" + return GenerationResult( + content=polished, + usage={"prompt_tokens": 50, "completion_tokens": 50, "total_tokens": 100}, + model=model or "mock-model", + ) + + # 否则返回脚本生成的 JSON 数据 + mock_shots = [ + { + "id": 1, + "type": "segment", + "scene": "镜头从门外缓缓推入,展示客厅整体布局,自然光从落地窗洒入", + "voiceover": "大家好,今天给大家讲讲家装验收最容易被忽略的5个细节", + "duration": "5s", + }, + { + "id": 2, + "type": "segment", + "scene": "特写墙面,手指划过检查平整度,展示一处细微裂纹", + "voiceover": "第一,墙面验收。很多人只看颜色,其实平整度和裂纹更重要", + "duration": "8s", + }, + { + "id": 3, + "type": "segment", + "scene": "蹲下来拍摄地板接缝处,展示踢脚线与地板的缝隙", + "voiceover": "第二,地板验收。重点看接缝是否均匀,踢脚线是否贴合", + "duration": "8s", + }, + { + "id": 4, + "type": "empty_shot", + "scene": "现代简约风格卫生间,白色瓷砖,柔和灯光,镜头缓慢平移", + "voiceover": "", + "duration": "3s", + }, + { + "id": 5, + "type": "segment", + "scene": "打开水龙头,检查水流和水压,特写地漏排水速度", + "voiceover": "第三,水电验收。测试所有开关、龙头,检查排水是否顺畅", + "duration": "8s", + }, + { + "id": 6, + "type": "segment", + "scene": "开关面板特写,逐一测试灯光开关,展示一处松动的面板", + "voiceover": "第四,电路验收。每个开关都要试,面板安装是否牢固", + "duration": "7s", + }, + { + "id": 7, + "type": "segment", + "scene": "主人公安慰地微笑,竖起大拇指,背景是温馨的客厅", + "voiceover": "记住这5点,验收不踩坑!关注我,更多家装干货等你", + "duration": "6s", + }, + ] + + return GenerationResult( + content=json.dumps(mock_shots, ensure_ascii=False), + usage={"prompt_tokens": 100, "completion_tokens": 200, "total_tokens": 300}, + model=model or "mock-model", + ) + + async def generate_stream( + self, + prompt: str, + model: str | None = None, + temperature: float = 0.7, + max_tokens: int | None = None, + **kwargs, + ) -> AsyncIterator[str]: + """模拟流式生成 - 返回脚本 JSON""" + import asyncio + import json + + # 检测是否为润色请求 + if "润色" in prompt or "polish" in prompt.lower(): + response = "【润色后】优化后的文案,更适合短视频口播!" + else: + # 返回脚本生成的 JSON 数据 + mock_shots = [ + { + "id": 1, + "type": "segment", + "scene": "主播站在毛坯房里,表情严肃", + "voiceover": "装修被坑了8万的业主,昨天来找我哭诉...", + "duration": "5s", + }, + { + "id": 2, + "type": "segment", + "scene": "主播指着墙面,手指划过", + "voiceover": "第一坑,水电改造!很多人图便宜找游击队", + "duration": "8s", + }, + { + "id": 3, + "type": "empty_shot", + "scene": "现代装修施工现场,水电管线整齐排列,4K画质", + "voiceover": "看,这就是专业施工", + "duration": "3s", + }, + ] + response = json.dumps(mock_shots, ensure_ascii=False) + + # 流式输出 + chunk_size = 10 # 每10个字符一个chunk + for i in range(0, len(response), chunk_size): + yield response[i : i + chunk_size] + await asyncio.sleep(0.05) # 模拟打字机效果 + + async def health_check(self, model: str | None = None) -> ModelHealth: + """模拟健康检查""" + return ModelHealth( + id=model or "mock-model", + name="Mock Model", + is_available=True, + response_time=50.0, + last_error=None, + ) + + @property + def available_models(self) -> list[str]: + return ["mock-model", "mock-gpt-3.5", "mock-gpt-4"] diff --git a/python-api/app/ai/providers/kling_dto.py b/python-api/app/ai/providers/kling_dto.py new file mode 100644 index 0000000..1f00bcc --- /dev/null +++ b/python-api/app/ai/providers/kling_dto.py @@ -0,0 +1,45 @@ +""" +Kling AI Provider DTO +===================== + +Provider 层数据模型,封装 Kling API 返回结构。 +禁止向业务层泄漏裸 dict[str, Any]。 +""" + +from pydantic import BaseModel, Field + +from app.schemas.enums import KlingTaskStatus + + +class KlingVideoResult(BaseModel): + """Kling 视频生成结果""" + + task_id: str | None = Field(None, alias="task_id") + task_status: KlingTaskStatus | None = Field(None, alias="task_status") + task_status_msg: str | None = Field(None, alias="task_status_msg") + task_result: dict | None = Field(None, alias="task_result") + + +class KlingImageResult(BaseModel): + """Kling 图片生成结果""" + + task_id: str | None = Field(None, alias="task_id") + task_status: KlingTaskStatus | None = Field(None, alias="task_status") + task_status_msg: str | None = Field(None, alias="task_status_msg") + task_result: dict | None = Field(None, alias="task_result") + + +class KlingVoiceResult(BaseModel): + """Kling 自定义音色结果""" + + task_id: str | None = Field(None, alias="task_id") + task_status: KlingTaskStatus | None = Field(None, alias="task_status") + task_result: dict | None = Field(None, alias="task_result") + + +class KlingElementResult(BaseModel): + """Kling 主体创建结果""" + + task_id: str | None = Field(None, alias="task_id") + task_status: KlingTaskStatus | None = Field(None, alias="task_status") + task_result: dict | None = Field(None, alias="task_result") diff --git a/python-api/app/ai/providers/klingai_provider.py b/python-api/app/ai/providers/klingai_provider.py new file mode 100644 index 0000000..0e12674 --- /dev/null +++ b/python-api/app/ai/providers/klingai_provider.py @@ -0,0 +1,1326 @@ +""" +KlingAI (可灵 AI) API Provider + +API 域名: https://api-beijing.klingai.com +认证方式: JWT (AK + SK) +""" + +import json +import logging +from typing import Any + +from pydantic import BaseModel, Field + +from app.core.token_manager import JWTTokenStrategy, TokenManager + +logger = logging.getLogger(__name__) + + +class KlingAIConfig(BaseModel): + """Kling AI Provider 配置""" + + access_key: str = Field(default="", description="Access Key") + secret_key: str = Field(default="", description="Secret Key") + base_url: str = Field(default="https://api-beijing.klingai.com", description="API Base URL") + + +class KlingPromptBuilder: + """Kling Prompt 构建器 + + 将业务语义(scene, voiceover, human_id)转换为 Kling API 专用语法。 + 所有 Kling 语法(<<>>, <<>>)仅限此类内部使用。 + """ + + @staticmethod + def omni_segment(scene: str, voiceover: str, human_id: str | None = None) -> str: + """构建 Omni-Video 分镜提示词""" + return f'<<>>{scene},说:"{voiceover}"' + + @staticmethod + def empty_shot(scene: str, voiceover: str) -> str: + """构建空镜图生视频提示词""" + if voiceover: + return f'{scene}。<<>>画外音:"{voiceover}"' + return scene + + +class KlingAIProvider: + """ + KlingAI API Provider + + 官方音色ID: + - 829824295735410756: 钓系女友 + - 829826751244537879: 温柔女声 + - 829826792415842333: 播报男声 + - 829826834144964676: 盐系少年 + - 829826884271091753: 撒娇女友 + """ + + provider_id = "klingai" + DEFAULT_BASE_URL = "https://api-beijing.klingai.com" + + # 官方预设音色 + PRESET_VOICES = { + "829824295735410756": "钓系女友", + "829826751244537879": "温柔女声", + "829826792415842333": "播报男声", + "829826834144964676": "盐系少年", + "829826884271091753": "撒娇女友", + } + + def __init__(self, config: dict[str, Any] = None): + self.config = config or {} + self.access_key = self.config.get("access_key", "") + self.secret_key = self.config.get("secret_key", "") + self.base_url = self.config.get("base_url", self.DEFAULT_BASE_URL).rstrip("/") + + # 初始化 Token 策略 + self._token_strategy: JWTTokenStrategy | None = None + if self.access_key and self.secret_key: + self._token_strategy = JWTTokenStrategy( + access_key=self.access_key, + secret_key=self.secret_key, + expires_in=1800, # 30分钟 + ) + + async def _get_headers(self) -> dict[str, str]: + """获取请求头(使用 TokenManager 缓存)""" + if not self._token_strategy: + raise ValueError("KlingAI access_key and secret_key are required") + + token_info = await TokenManager.get_instance().get_token(self._token_strategy) + return { + "Authorization": f"Bearer {token_info.token}", + "Content-Type": "application/json", + } + + # ==================== 文生视频 ==================== + + # ==================== Omni 视频生成 ==================== + + async def generate_video_omni( + self, + prompt: str, + model: str = "kling-v3-omni", + mode: str = "pro", + aspect_ratio: str = "9:16", + duration: int | None = None, + sound: str = "on", + negative_prompt: str | None = None, + multi_shot: bool = False, + shot_type: str | None = None, + multi_prompt: list[dict | None] = None, + image_list: list[dict | None] = None, + element_list: list[dict | None] = None, + video_list: list[dict | None] = None, + voice_list: list[dict | None] = None, + callback_url: str | None = None, + external_task_id: str | None = None, + **kwargs, + ) -> dict[str, Any]: + """Omni-Video 视频生成(多模态视频生成) + + POST /v1/videos/omni-video + + 支持文本、图片、主体、视频多种输入方式组合生成视频。 + 是 kling-v3-omni 和 kling-video-o1 模型的专用接口。 + + Args: + prompt: 正向提示词(≤2500字符),支持 <<>>/<<>>/<<>> 引用语法 + model: 模型名称,kling-v3-omni 或 kling-video-o1 + mode: 生成模式,pro=1080p(默认), std=720p + aspect_ratio: 宽高比,可选 16:9/9:16/1:1 + duration: 时长,3/5/10/15(秒),Omni 支持 3-15s + sound: 声音控制,on=音画同出, off=无声 + negative_prompt: 负向提示词 + multi_shot: 是否多镜头模式 + shot_type: 分镜方式,customize=自定义, intelligence=智能分镜 + multi_prompt: 多镜头提示词列表,每个元素包含 index, prompt, duration + image_list: 参考图片列表,最多 4 张 + element_list: 主体参考列表,最多 7 个,格式:[{"element_id": 123}] + video_list: 参考视频列表 + callback_url: 回调地址 + external_task_id: 自定义任务ID + + Returns: + 包含 task_id 和任务状态的字典 + """ + import aiohttp + + url = f"{self.base_url}/v1/videos/omni-video" + + payload = { + "model_name": model, + "prompt": prompt, + "mode": mode, + "aspect_ratio": aspect_ratio, + "sound": sound, + } + if duration is not None: + payload["duration"] = str(duration) + + if negative_prompt: + payload["negative_prompt"] = negative_prompt + + if multi_shot: + payload["multi_shot"] = True + if shot_type: + payload["shot_type"] = shot_type + if multi_prompt: + payload["multi_prompt"] = multi_prompt + + if image_list: + payload["image_list"] = image_list + if element_list: + payload["element_list"] = element_list + if video_list: + payload["video_list"] = video_list + if voice_list: + payload["voice_list"] = voice_list + if callback_url: + payload["callback_url"] = callback_url + if external_task_id: + payload["external_task_id"] = external_task_id + + # 记录请求参数(调试用) + logger.info( + f"[KlingAI] omni-video 请求: {json.dumps(payload, ensure_ascii=False, indent=2)}" + ) + + async with ( + aiohttp.ClientSession() as session, + session.post(url, json=payload, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + logger.info(f"[KlingAI] omni-video 响应: {json.dumps(data, ensure_ascii=False)}") + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + result_data = data.get("data", {}) + if isinstance(result_data, list) and len(result_data) > 0: + return result_data[0] if isinstance(result_data[0], dict) else {} + return result_data if isinstance(result_data, dict) else {} + + async def get_omni_video_task(self, task_id: str, **kwargs) -> dict[str, Any]: + """查询 Omni-Video 任务状态 + + GET /v1/videos/omni-video/{task_id} + + Args: + task_id: 任务ID + """ + import aiohttp + + url = f"{self.base_url}/v1/videos/omni-video/{task_id}" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + result_data = data.get("data", {}) + if isinstance(result_data, list) and len(result_data) > 0: + return result_data[0] if isinstance(result_data[0], dict) else {} + return result_data if isinstance(result_data, dict) else {} + + async def list_omni_video_tasks( + self, page: int = 1, page_size: int = 30, **kwargs + ) -> dict[str, Any]: + """查询 Omni-Video 任务列表 + + GET /v1/videos/omni-video?pageNum={page}&pageSize={page_size} + + Args: + page: 页码 + page_size: 每页数量 + """ + import aiohttp + + url = f"{self.base_url}/v1/videos/omni-video?pageNum={page}&pageSize={page_size}" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + # ==================== 文生视频 ==================== + + async def generate_video_text2video( + self, + prompt: str, + model: str = "kling-v3", + mode: str = "pro", + aspect_ratio: str = "9:16", + duration: int | None = None, + sound: str = "on", + negative_prompt: str | None = None, + multi_shot: bool = False, + shot_type: str | None = None, + multi_prompt: list[dict | None] = None, + element_list: list[dict | None] = None, + voice_list: list[dict | None] = None, + callback_url: str | None = None, + external_task_id: str | None = None, + **kwargs, + ) -> dict[str, Any]: + """文生视频 + + POST /v1/videos/text2video + + Args: + prompt: 正向提示词(≤2500字符),支持 <<>> 引用语法 + model: 模型名称,可选 kling-v1/kling-v1-6/kling-v2-master/kling-v2-1-master/kling-v2-5-turbo/kling-v2-6/kling-v3 + mode: 生成模式,pro=1080p(默认), std=720p + aspect_ratio: 宽高比,可选 16:9/9:16/1:1/4:3/3:4 + duration: 时长,可选 3/5/10(秒) + sound: 声音控制,on/off(kling-v3支持) + negative_prompt: 负向提示词 + multi_shot: 是否多镜头 + shot_type: 分镜方式,customize/intelligence + multi_prompt: 多镜头提示词列表,每个元素包含 index, prompt, duration + element_list: 主体参考列表,格式: [{"element_id": 123}], kling-v3支持 + voice_list: 音色列表,格式: [{"voice_id": "xxx"}], 配合 prompt 中 <<>> 使用 + callback_url: 回调地址 + external_task_id: 自定义任务ID + """ + import aiohttp + + url = f"{self.base_url}/v1/videos/text2video" + + payload = { + "model_name": model, + "prompt": prompt, + "mode": mode, + "aspect_ratio": aspect_ratio, + "sound": sound, + } + if duration is not None: + payload["duration"] = str(duration) + + if negative_prompt: + payload["negative_prompt"] = negative_prompt + + if multi_shot: + payload["multi_shot"] = True + if shot_type: + payload["shot_type"] = shot_type + if multi_prompt: + payload["multi_prompt"] = multi_prompt + + if element_list: + payload["element_list"] = element_list + if voice_list: + payload["voice_list"] = voice_list + if callback_url: + payload["callback_url"] = callback_url + if external_task_id: + payload["external_task_id"] = external_task_id + + async with ( + aiohttp.ClientSession() as session, + session.post(url, json=payload, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + logger.info(f"KlingAI text2video response: {data}") + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + result_data = data.get("data", {}) + if isinstance(result_data, list) and len(result_data) > 0: + return result_data[0] if isinstance(result_data[0], dict) else {} + return result_data if isinstance(result_data, dict) else {} + + # ==================== 图生视频 ==================== + + async def generate_video_image2video( + self, + prompt: str, + image_url: str, + model: str = "kling-v3", + mode: str = "pro", + duration: int | str | None = None, + image_tail_url: str | None = None, + camera_control: dict | None = None, + static_mask: str | None = None, + dynamic_masks: list[dict | None] = None, + voice_list: list[dict | None] = None, + sound: str = "on", + callback_url: str | None = None, + **kwargs, + ) -> dict[str, Any]: + """图生视频 + + Args: + prompt: 视频运动描述 + image_url: 首帧图像URL或Base64 + model: 模型名称 + mode: 生成模式,pro=1080p(默认), std=720p + duration: 视频时长 + image_tail_url: 尾帧图像URL或Base64 + camera_control: 相机控制参数,仅kling-v1模型支持 + static_mask: 静态笔刷涂抹区域 + dynamic_masks: 动态笔刷配置列表,包含mask和trajectories + voice_list: 音色列表,格式: [{"voice_id": "xxx"}], 配合 prompt 中 <<>> 使用 + sound: 声音控制,on/off(kling-v2-6支持) + callback_url: 回调地址 + """ + import aiohttp + + url = f"{self.base_url}/v1/videos/image2video" + + payload = { + "model_name": model, + "image": image_url, + "prompt": prompt, + "mode": mode, + "sound": sound, + } + if duration is not None: + payload["duration"] = str(duration) + + if image_tail_url: + payload["image_tail"] = image_tail_url + if camera_control: + payload["camera_control"] = camera_control + if static_mask: + payload["static_mask"] = static_mask + if dynamic_masks: + payload["dynamic_masks"] = dynamic_masks + if voice_list: + payload["voice_list"] = voice_list + if callback_url: + payload["callback_url"] = callback_url + + # 记录请求参数(调试用) + logger.info( + f"[KlingAI] image2video 请求: {json.dumps(payload, ensure_ascii=False, indent=2)}" + ) + + async with ( + aiohttp.ClientSession() as session, + session.post(url, json=payload, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + logger.info(f"[KlingAI] image2video 响应: {json.dumps(data, ensure_ascii=False)}") + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + # ==================== 视频延长 ==================== + + async def extend_video( + self, + video_id: str, + prompt: str, + duration: int = 5, + model: str = "kling-v1-6", + callback_url: str | None = None, + **kwargs, + ) -> dict[str, Any]: + """视频延长 + + Args: + video_id: 要延长的视频ID + prompt: 延长部分的提示词 + duration: 延长时间(5或10秒) + model: 模型名称 + callback_url: 回调地址 + """ + import aiohttp + + url = f"{self.base_url}/v1/videos/extend" + + payload = { + "model_name": model, + "video_id": video_id, + "prompt": prompt, + "duration": duration, + } + + if callback_url: + payload["callback_url"] = callback_url + + async with ( + aiohttp.ClientSession() as session, + session.post(url, json=payload, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + # ==================== 人脸识别 ==================== + + async def identify_face( + self, video_id: str | None = None, video_url: str | None = None, **kwargs + ) -> dict[str, Any]: + """人脸识别(对口型前置步骤) + + POST /v1/videos/identify-face + + Args: + video_id: KlingAI生成的视频ID + video_url: 上传的视频URL(二选一) + + Returns: + 包含 session_id 和 face_data 的字典 + """ + import aiohttp + + url = f"{self.base_url}/v1/videos/identify-face" + + payload = {} + if video_id: + payload["video_id"] = video_id + elif video_url: + payload["video_url"] = video_url + else: + raise ValueError("必须提供 video_id 或 video_url") + + async with ( + aiohttp.ClientSession() as session, + session.post(url, json=payload, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + # ==================== 对口型(新版 advanced-lip-sync)==================== + + async def advanced_lip_sync( + self, + session_id: str, + face_choose: list[dict[str, Any]], + callback_url: str | None = None, + **kwargs, + ) -> dict[str, Any]: + """新版对口型视频生成 + + POST /v1/videos/advanced-lip-sync + + Args: + session_id: 人脸识别返回的会话ID + face_choose: 人脸选择配置列表,每项包含: + - face_id: 人脸ID(必填) + - audio_id: 通过TTS试听接口生成的音频ID(与sound_file二选一) + - sound_file: 音频文件URL或Base64(与audio_id二选一) + - sound_start_time: 音频裁剪起点时间(ms,必填) + - sound_end_time: 音频裁剪终点时间(ms,必填) + - sound_insert_time: 裁剪后音频插入时间(ms,必填) + callback_url: 回调地址 + + Returns: + 包含 task_id 的任务信息 + """ + import aiohttp + + url = f"{self.base_url}/v1/videos/advanced-lip-sync" + + payload = { + "session_id": session_id, + "face_choose": face_choose, + } + + if callback_url: + payload["callback_url"] = callback_url + + async with ( + aiohttp.ClientSession() as session, + session.post(url, json=payload, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + result_data = data.get("data", {}) + return result_data if isinstance(result_data, dict) else {} + + async def get_advanced_lip_sync_task(self, task_id: str, **kwargs) -> dict[str, Any]: + """查询新版对口型任务状态 + + GET /v1/videos/advanced-lip-sync/{task_id} + """ + import aiohttp + + url = f"{self.base_url}/v1/videos/advanced-lip-sync/{task_id}" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + # ==================== 自定义音色 ==================== + + async def create_custom_voice( + self, + voice_name: str, + audio_url: str | None = None, + video_url: str | None = None, + video_id: str | None = None, + callback_url: str | None = None, + external_task_id: str | None = None, + **kwargs, + ) -> dict[str, Any]: + """创建自定义音色 + + Args: + voice_name: 音色名称(≤20字符) + audio_url: 音频文件URL(5-30秒,mp3/wav格式) + video_url: 视频文件URL(可选) + video_id: 历史作品ID(可选) + callback_url: 回调地址 + external_task_id: 自定义任务ID + + 音频要求: 人声干净、无杂音、单一人声、5-30秒 + """ + import aiohttp + + url = f"{self.base_url}/v1/general/custom-voices" + + payload = { + "voice_name": voice_name, + } + + # 三者至少填一个 (KlingAI API 使用 voice_url 参数名) + if audio_url: + payload["voice_url"] = audio_url + elif video_url: + payload["voice_url"] = video_url # KlingAI 使用 voice_url 而不是 video_url + elif video_id: + payload["video_id"] = video_id + else: + raise ValueError("必须提供 audio_url、video_url 或 video_id 之一") + + if callback_url: + payload["callback_url"] = callback_url + if external_task_id: + payload["external_task_id"] = external_task_id + + async with ( + aiohttp.ClientSession() as session, + session.post(url, json=payload, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + async def list_custom_voices(self, **kwargs) -> list[dict[str, Any]]: + """查询自定义音色列表 + + Returns: + 自定义音色列表,每个音色包含 voice_id, voice_name, status 等字段 + """ + import aiohttp + + url = f"{self.base_url}/v1/general/custom-voices" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + logger.info(f"KlingAI list_custom_voices response: {data}") + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + # KlingAI API 返回的是任务列表,每个任务包含 task_result.voices + result_data = data.get("data", []) + voices = [] + if isinstance(result_data, list): + for task in result_data: + if isinstance(task, dict) and task.get("task_status") == "succeed": + task_result = task.get("task_result", {}) + if isinstance(task_result, dict): + voice_list = task_result.get("voices", []) + if isinstance(voice_list, list): + voices.extend(voice_list) + return voices + + async def list_preset_voices(self, **kwargs) -> list[dict[str, Any]]: + """查询官方音色列表 + + KlingAI API 返回的是任务列表格式,每个任务包含音色信息: + { + 'code': 0, + 'data': [ + { + 'task_id': '...', + 'task_status': 'succeed', + 'task_result': {'voices': [{'voice_id': '...', 'voice_name': '...'}]} + } + ] + } + """ + import aiohttp + + url = f"{self.base_url}/v1/general/presets-voices" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + + # 解析任务列表格式的响应 + voices = [] + task_list = data.get("data", []) + for task in task_list: + if task.get("task_status") == "succeed": + task_result = task.get("task_result", {}) + voice_list = task_result.get("voices", []) + voices.extend(voice_list) + + return voices + + async def delete_custom_voice(self, voice_id: str, **kwargs) -> dict[str, Any]: + """删除自定义音色 + + Args: + voice_id: 音色ID + """ + import aiohttp + + url = f"{self.base_url}/v1/general/delete-voices" + + payload = {"voice_id": voice_id} + + async with ( + aiohttp.ClientSession() as session, + session.post(url, json=payload, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + # ==================== 任务查询 ==================== + + async def get_video_task( + self, task_id: str, task_type: str = "text2video", **kwargs + ) -> dict[str, Any]: + """查询视频任务状态 + + Args: + task_id: 任务ID + task_type: 任务类型,可选 text2video/image2video/lip-sync + + Returns: + 任务详情,包含 task_status 字段: + - submitted: 已提交 + - processing: 处理中 + - succeed: 成功 + - failed: 失败 + """ + import aiohttp + + endpoint_map = { + "text2video": "videos/text2video", + "image2video": "videos/image2video", + "lip-sync": "videos/lip-sync", + "extend": "videos/extend", + } + + endpoint = endpoint_map.get(task_type, "videos/text2video") + url = f"{self.base_url}/v1/{endpoint}/{task_id}" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + async def list_video_tasks( + self, + task_type: str = "text2video", + page: int = 1, + page_size: int = 10, + **kwargs, + ) -> dict[str, Any]: + """查询视频任务列表 + + Args: + task_type: 任务类型 + page: 页码 + page_size: 每页数量 + """ + import aiohttp + + endpoint_map = { + "text2video": "videos/text2video", + "image2video": "videos/image2video", + } + + endpoint = endpoint_map.get(task_type, "videos/text2video") + url = f"{self.base_url}/v1/{endpoint}?page={page}&page_size={page_size}" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + async def get_custom_voice_task(self, task_id: str, **kwargs) -> dict[str, Any]: + """查询自定义音色任务状态""" + import aiohttp + + url = f"{self.base_url}/v1/general/custom-voices/{task_id}" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + # ==================== TTS 语音合成 ==================== + + async def generate_tts( + self, + text: str, + voice_id: str, + voice_language: str = "zh", + voice_speed: float = 1.0, + **kwargs, + ) -> dict[str, Any]: + """ + 语音合成 (TTS) + + POST /v1/audio/tts + + Args: + text: 要合成的文本 + voice_id: 音色ID(官方预设或自定义音色) + voice_language: 语言 (zh/en) + voice_speed: 语速 (0.8-2.0) + + Returns: + 包含音频URL和任务信息的字典 + """ + import aiohttp + + url = f"{self.base_url}/v1/audio/tts" + + payload = { + "text": text, + "voice_id": voice_id, + "voice_language": voice_language, + "voice_speed": str(voice_speed), + } + + async with ( + aiohttp.ClientSession() as session, + session.post(url, json=payload, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI TTS API error: {data.get('message')}") + return data.get("data", {}) + + async def get_tts_task(self, task_id: str, **kwargs) -> dict[str, Any]: + """查询 TTS 任务状态""" + import aiohttp + + url = f"{self.base_url}/v1/audio/tts/{task_id}" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + async def list_tts_tasks(self, page: int = 1, page_size: int = 30, **kwargs) -> dict[str, Any]: + """ + 查询 TTS 任务列表 + + Args: + page: 页码 + page_size: 每页数量 + + Returns: + 任务列表数据 + """ + import aiohttp + + url = f"{self.base_url}/v1/audio/tts?pageNum={page}&pageSize={page_size}" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + # ==================== 主体管理 (Element) ==================== + + async def create_element( + self, + element_name: str, + element_description: str, + reference_type: str = "image_refer", + element_image_list: dict[str, Any | None] = None, + element_video_list: dict[str, Any | None] = None, + element_voice_id: str | None = None, + callback_url: str | None = None, + **kwargs, + ) -> dict[str, Any]: + """创建主体(自定义元素) + + POST /v1/general/advanced-custom-elements + + Args: + element_name: 主体名称(≤20字符) + element_description: 主体描述(≤100字符) + reference_type: 参考类型,image_refer(图片参考) / video_refer(视频参考) + element_image_list: 图片参考对象,图片定制时必填 + 格式: { + "frontal_image": "正面图URL", + "refer_images": [{"image_url": "其他角度URL1"}, ...] + } + 要求:正面图+1-3张其他角度,jpg/jpeg/png,≤10MB + element_video_list: 视频参考对象,视频定制时必填 + 格式: { + "frontal_video": "正面视频URL", + "refer_videos": [{"video_url": "其他角度URL"}] + } + 要求:3s~8s,MP4/MOV,高度720px~2160px,≤200MB + 限制:仅支持写实风格、人形主体 + element_voice_id: 音色ID,绑定音色到主体 + callback_url: 回调地址 + + Returns: + 包含 task_id 和任务状态的字典 + """ + import aiohttp + + url = f"{self.base_url}/v1/general/advanced-custom-elements" + + payload = { + "element_name": element_name, + "element_description": element_description, + "reference_type": reference_type, + } + + # 修正 element_image_list 内部格式:refer_images 数组元素使用 image_url 而非 url + if element_image_list and isinstance(element_image_list, dict): + formatted_image_list = {"frontal_image": element_image_list.get("frontal_image", "")} + refer_images = element_image_list.get("refer_images", []) + if refer_images: + formatted_image_list["refer_images"] = [ + ( + {"image_url": img.get("url", img.get("image_url", ""))} + if isinstance(img, dict) + else {"image_url": img} + ) + for img in refer_images + ] + payload["element_image_list"] = formatted_image_list + # 修正 element_video_list 内部格式 + # element_video_list 是对象格式,不是数组 + # 正确格式: {"refer_videos": [{"video_url": "..."}]} + if element_video_list and isinstance(element_video_list, dict): + formatted_video_list = {} + + # 处理 refer_videos 数组 + refer_videos = element_video_list.get("refer_videos", []) + if refer_videos: + formatted_video_list["refer_videos"] = [ + ( + {"video_url": vid.get("url", vid.get("video_url", ""))} + if isinstance(vid, dict) + else {"video_url": vid} + ) + for vid in refer_videos + ] + + # 处理 frontal_video(如果提供了)- 转为 refer_videos 格式 + frontal_video = element_video_list.get("frontal_video", "") + if frontal_video: + if "refer_videos" not in formatted_video_list: + formatted_video_list["refer_videos"] = [] + formatted_video_list["refer_videos"].append({"video_url": frontal_video}) + + if formatted_video_list: + # API 要求 element_video_list 是对象格式 + payload["element_video_list"] = formatted_video_list + if element_voice_id: + payload["element_voice_id"] = element_voice_id + if callback_url: + payload["callback_url"] = callback_url + + async with ( + aiohttp.ClientSession() as session, + session.post(url, json=payload, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + logger.info(f"KlingAI create_element response: {data}") + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + result_data = data.get("data", {}) + if isinstance(result_data, list) and len(result_data) > 0: + return result_data[0] if isinstance(result_data[0], dict) else {} + return result_data if isinstance(result_data, dict) else {} + + async def list_elements(self, **kwargs) -> list[dict[str, Any]]: + """查询主体列表 + + GET /v1/general/advanced-custom-elements + + KlingAI API 返回任务列表格式,每个任务包含 task_result.elements 数组。 + 需要从任务列表中提取所有主体信息。 + + Returns: + 主体列表,每个主体包含 element_id, element_name 等字段 + """ + import aiohttp + + url = f"{self.base_url}/v1/general/advanced-custom-elements" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + logger.info(f"KlingAI list_elements response: {data}") + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + + # KlingAI 返回任务列表,每个任务包含 task_result.elements + task_list = data.get("data", []) + elements = [] + + for task in task_list: + if isinstance(task, dict) and task.get("task_status") == "succeed": + task_result = task.get("task_result", {}) + if isinstance(task_result, dict): + element_list = task_result.get("elements", []) + if isinstance(element_list, list): + for element in element_list: + # 添加任务信息到主体数据中 + element["task_id"] = task.get("task_id") + element["task_status"] = task.get("task_status") + element["created_at"] = task.get("created_at") + element["updated_at"] = task.get("updated_at") + elements.append(element) + + return elements + + async def get_element_task(self, task_id: str, **kwargs) -> dict[str, Any]: + """查询创建主体任务状态 + + GET /v1/general/advanced-custom-elements/{task_id} + + Args: + task_id: 创建任务的ID + """ + import aiohttp + + url = f"{self.base_url}/v1/general/advanced-custom-elements/{task_id}" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + logger.info(f"KlingAI get_element_task response: {data}") + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + result_data = data.get("data", {}) + return result_data if isinstance(result_data, dict) else {} + + async def get_element(self, element_id: str, **kwargs) -> dict[str, Any]: + """查询单个主体详情 + + GET /v1/general/advanced-custom-elements/{element_id} + + Args: + element_id: 主体ID + """ + import aiohttp + + url = f"{self.base_url}/v1/general/advanced-custom-elements/{element_id}" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + logger.info(f"KlingAI get_element response: {data}") + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + result_data = data.get("data", {}) + return result_data if isinstance(result_data, dict) else {} + + async def delete_element(self, element_id: str, **kwargs) -> dict[str, Any]: + """删除主体 + + POST /v1/general/delete-elements + + Args: + element_id: 主体ID + """ + import aiohttp + + url = f"{self.base_url}/v1/general/delete-elements" + + payload = {"element_id": element_id} + + async with ( + aiohttp.ClientSession() as session, + session.post(url, json=payload, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + logger.info(f"KlingAI delete_element response: {data}") + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + result_data = data.get("data", {}) + return result_data if isinstance(result_data, dict) else {} + + # ==================== 智能补全主体图 ==================== + + async def ai_multi_shot( + self, frontal_image: str, callback_url: str | None = None, **kwargs + ) -> dict[str, Any]: + """智能补全主体不同角度图片(AI Multi Shot) + + POST /v1/general/ai-multi-shot + + 通过主体正面图,自动推理出该主体其他角度图片。 + 每次可生成3组结果供选择,每次扣减0.5积分。 + + Args: + frontal_image: 主体正面参考图 URL + callback_url: 回调地址 + + Returns: + 包含生成结果的任务信息 + """ + import aiohttp + + url = f"{self.base_url}/v1/general/ai-multi-shot" + + payload = { + "element_frontal_image": frontal_image, + } + + if callback_url: + payload["callback_url"] = callback_url + + async with ( + aiohttp.ClientSession() as session, + session.post(url, json=payload, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + logger.info(f"KlingAI ai_multi_shot response: {data}") + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + result_data = data.get("data", {}) + return result_data if isinstance(result_data, dict) else {} + + async def get_ai_multi_shot_task(self, task_id: str, **kwargs) -> dict[str, Any]: + """查询智能补全主体图任务状态 + + GET /v1/general/ai-multi-shot/{task_id} + + Args: + task_id: 任务ID + """ + import aiohttp + + url = f"{self.base_url}/v1/general/ai-multi-shot/{task_id}" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + # ==================== Omni-Image 图像生成 ==================== + + async def generate_omni_image( + self, + prompt: str, + model: str = "kling-image-o1", + aspect_ratio: str = "9:16", + resolution: str = "1k", + result_type: str = "single", + n: int = 1, + element_list: list[dict[str, Any]] | None = None, + image_list: list[dict[str, Any]] | None = None, + callback_url: str | None = None, + **kwargs, + ) -> dict[str, Any]: + """Omni-Image 图像生成 + + POST /v1/images/omni-image + + Args: + prompt: 正向提示词 + model: 模型名称,kling-image-o1 或 kling-v3-omni + aspect_ratio: 宽高比,可选 16:9/9:16/1:1/4:3/3:4 + resolution: 清晰度,1k/2k/4k + result_type: 结果类型,single/series + n: 生成图片数量(1-9) + element_list: 主体参考列表 + image_list: 参考图列表 + callback_url: 回调地址 + """ + import aiohttp + + url = f"{self.base_url}/v1/images/omni-image" + + payload: dict[str, Any] = { + "model_name": model, + "prompt": prompt, + "aspect_ratio": aspect_ratio, + "resolution": resolution, + "result_type": result_type, + "n": n, + } + + if element_list: + payload["element_list"] = element_list + if image_list: + payload["image_list"] = image_list + if callback_url: + payload["callback_url"] = callback_url + + logger.info( + f"[KlingAI] omni-image 请求: {json.dumps(payload, ensure_ascii=False, indent=2)}" + ) + + async with ( + aiohttp.ClientSession() as session, + session.post(url, json=payload, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + logger.info(f"[KlingAI] omni-image 响应: {json.dumps(data, ensure_ascii=False)}") + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + result_data = data.get("data", {}) + if isinstance(result_data, list) and len(result_data) > 0: + return result_data[0] if isinstance(result_data[0], dict) else {} + return result_data if isinstance(result_data, dict) else {} + + async def get_omni_image_task(self, task_id: str, **kwargs) -> dict[str, Any]: + """查询 Omni-Image 任务状态 + + GET /v1/images/omni-image/{task_id} + """ + import aiohttp + + url = f"{self.base_url}/v1/images/omni-image/{task_id}" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + # ==================== 文生图 ==================== + + async def generate_image( + self, + prompt: str, + model: str = "kling-v3", + aspect_ratio: str = "9:16", + negative_prompt: str | None = None, + callback_url: str | None = None, + **kwargs, + ) -> dict[str, Any]: + """文生图 + + POST /v1/images/generations + + Args: + prompt: 正向提示词 + model: 模型名称,kling-v3 + aspect_ratio: 宽高比,可选 16:9/9:16/1:1/4:3/3:4 + negative_prompt: 负向提示词 + callback_url: 回调地址 + """ + import aiohttp + + url = f"{self.base_url}/v1/images/generations" + + payload = { + "model_name": model, + "prompt": prompt, + "aspect_ratio": aspect_ratio, + } + + if negative_prompt: + payload["negative_prompt"] = negative_prompt + if callback_url: + payload["callback_url"] = callback_url + + # 记录请求参数(调试用) + logger.info( + f"[KlingAI] generate_image 请求: {json.dumps(payload, ensure_ascii=False, indent=2)}" + ) + + async with ( + aiohttp.ClientSession() as session, + session.post(url, json=payload, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + logger.info(f"[KlingAI] generate_image 响应: {json.dumps(data, ensure_ascii=False)}") + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + result_data = data.get("data", {}) + if isinstance(result_data, list) and len(result_data) > 0: + return result_data[0] if isinstance(result_data[0], dict) else {} + return result_data if isinstance(result_data, dict) else {} + + async def get_image_task(self, task_id: str, **kwargs) -> dict[str, Any]: + """查询文生图任务状态 + + GET /v1/images/generations/{task_id} + + Args: + task_id: 任务ID + """ + import aiohttp + + url = f"{self.base_url}/v1/images/generations/{task_id}" + + async with ( + aiohttp.ClientSession() as session, + session.get(url, headers=await self._get_headers()) as resp, + ): + data = await resp.json() + if data.get("code") != 0: + raise Exception(f"KlingAI API error: {data.get('message')}") + return data.get("data", {}) + + +# 导出 Provider 类 +__all__ = ["KlingAIProvider", "KlingAIConfig"] diff --git a/python-api/app/ai/providers/volcengine_provider.py b/python-api/app/ai/providers/volcengine_provider.py new file mode 100644 index 0000000..9465698 --- /dev/null +++ b/python-api/app/ai/providers/volcengine_provider.py @@ -0,0 +1,464 @@ +""" +火山方舟官方 SDK Provider +========================== + +基于火山方舟官方 Python SDK 实现,支持: +- 文本生成 (Chat Completions) +- 流式输出 +- 图片生成 +- 向量化 +- 深度思考 +- 工具调用 + +安装依赖: + pip install 'volcengine-python-sdk[ark]' + +文档: + https://www.volcengine.com/docs/82379 +""" + +from __future__ import annotations + +import logging +import time +from collections.abc import AsyncIterator + +from app.ai.providers.base import ( + GenerationResult, + LLMProvider, + ModelHealth, + ProviderError, +) + +logger = logging.getLogger(__name__) + +# 尝试导入火山方舟 SDK +try: + from volcenginesdkarkruntime import Ark + + VOLCENGINE_SDK_AVAILABLE = True +except ImportError: + VOLCENGINE_SDK_AVAILABLE = False + logger.warning("火山方舟 SDK 未安装,请运行: pip install 'volcengine-python-sdk[ark]'") + + +class VolcengineProvider(LLMProvider): + """ + 火山方舟官方 SDK Provider + + 支持多模态能力: + - 文本对话 (Chat Completions) + - 图片生成 (Image Generation) + - 向量化 (Embeddings) + - 深度思考 (Reasoning) + """ + + provider_id = "volcengine" + provider_name = "火山方舟" + + # 默认配置 + DEFAULT_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3" + DEFAULT_TIMEOUT = 1800 # 秒,方舟推荐 1800 秒以上 + + # 模型 ID 映射(从配置文件加载) + PRESET_MODELS: dict[str, str] = {} + + @classmethod + def load_models_from_config(cls, models: list[dict]): + """ + 从配置文件加载模型列表 + + Args: + models: 模型列表,每个模型包含 model_name 字段 + """ + cls.PRESET_MODELS = {} + for model in models: + model_id = model.get("model_name") + model_alias = model.get("id") + if model_id and model_alias: + cls.PRESET_MODELS[model_alias] = model_id + + # 确保至少有一个默认模型 + if not cls.PRESET_MODELS: + cls.PRESET_MODELS = { + "doubao-seed-2-0-lite": "doubao-seed-2-0-lite-260215", + } + + logger.info(f"已加载 {len(cls.PRESET_MODELS)} 个模型: {list(cls.PRESET_MODELS.keys())}") + + def __init__( + self, + api_key: str | None = None, + base_url: str | None = None, + timeout: int = DEFAULT_TIMEOUT, + default_model: str | None = None, + **kwargs, + ): + """ + 初始化火山方舟 Provider + + Args: + api_key: API Key,从火山方舟控制台获取 + base_url: API 基础地址,默认北京节点 + timeout: 请求超时时间(秒) + default_model: 默认模型(Model ID) + """ + super().__init__(api_key, base_url, **kwargs) + + if not VOLCENGINE_SDK_AVAILABLE: + raise ProviderError( + "火山方舟 SDK 未安装,请运行: pip install 'volcengine-python-sdk[ark]'", + provider_id=self.provider_id, + ) + + if not self.api_key: + raise ProviderError("火山方舟 API Key 未配置", provider_id=self.provider_id) + + self.timeout = timeout + # 使用模型 ID 映射(自动映射) + if default_model: + self.default_model = self.PRESET_MODELS.get(default_model, default_model) + elif self.PRESET_MODELS: + # 兜底:使用 doubao-seed-2-0-lite 或第一个可用的模型 + self.default_model = self.PRESET_MODELS.get( + "doubao-seed-2-0-lite", list(self.PRESET_MODELS.values())[0] + ) + else: + # 兜底:使用一个默认模型ID(如果用户未配置任何模型) + self.default_model = "doubao-seed-2-0-lite-260215" + + self.client = self._create_client() + + def _create_client(self) -> Ark: + """创建火山方舟客户端""" + return Ark( + api_key=self.api_key, + base_url=self.base_url or self.DEFAULT_BASE_URL, + timeout=self.timeout, + ) + + async def generate( + self, + prompt: str, + model: str | None = None, + temperature: float = 0.7, + max_tokens: int | None = None, + system_prompt: str | None = None, + **kwargs, + ) -> GenerationResult: + """ + 同步生成文本 + + Args: + prompt: 用户提示词 + model: 模型 ID(如 doubao-seed-2-0-pro-260215) + temperature: 随机性 (0-2) + max_tokens: 最大生成 token 数 + system_prompt: 系统提示词(可选) + **kwargs: 额外参数(如 enable_thinking 启用深度思考) + + Returns: + GenerationResult: 生成结果 + """ + try: + # 构建消息 + messages = [] + if system_prompt: + messages.append({"role": "system", "content": system_prompt}) + messages.append({"role": "user", "content": prompt}) + + # 映射模型名称到模型 ID + model_id = self.PRESET_MODELS.get(model, model) if model else self.default_model + + # 构建请求参数 + request_params = { + "model": model_id, + "messages": messages, + "temperature": temperature, + } + + if max_tokens: + request_params["max_tokens"] = max_tokens + + # 额外参数(如深度思考) + if "enable_thinking" in kwargs: + request_params["extra_body"] = {"enable_thinking": kwargs["enable_thinking"]} + + # 调用 API + completion = self.client.chat.completions.create(**request_params) + + # 解析结果 + content = completion.choices[0].message.content or "" + usage = None + if completion.usage: + usage = { + "prompt_tokens": completion.usage.prompt_tokens, + "completion_tokens": completion.usage.completion_tokens, + "total_tokens": completion.usage.total_tokens, + } + + return GenerationResult( + content=content, + usage=usage, + model=completion.model or model or self.default_model, + ) + + except Exception as e: + raise ProviderError( + f"火山方舟生成失败: {str(e)}", provider_id=self.provider_id, original_error=e + ) + + async def generate_stream( + self, + prompt: str, + model: str | None = None, + temperature: float = 0.7, + max_tokens: int | None = None, + system_prompt: str | None = None, + **kwargs, + ) -> AsyncIterator[str]: + """ + 流式生成文本 + + Args: + prompt: 用户提示词 + model: 模型名称 + temperature: 随机性 + max_tokens: 最大 token 数 + system_prompt: 系统提示词(可选) + **kwargs: 额外参数 + + Yields: + str: 生成的文本片段 + """ + try: + messages = [] + if system_prompt: + messages.append({"role": "system", "content": system_prompt}) + messages.append({"role": "user", "content": prompt}) + model_id = self.PRESET_MODELS.get(model, model) if model else self.default_model + + request_params = { + "model": model_id, + "messages": messages, + "temperature": temperature, + "stream": True, + } + + if max_tokens: + request_params["max_tokens"] = max_tokens + + stream = self.client.chat.completions.create(**request_params) + + for chunk in stream: + if chunk.choices and chunk.choices[0].delta.content: + yield chunk.choices[0].delta.content + + except Exception as e: + raise ProviderError( + f"火山方舟流式生成失败: {str(e)}", provider_id=self.provider_id, original_error=e + ) + + async def generate_stream_with_progress( + self, + prompt: str, + model: str | None = None, + temperature: float = 0.7, + max_tokens: int | None = 8000, + system_prompt: str | None = None, + **kwargs, + ) -> AsyncIterator[dict]: + """ + 流式生成文本,带进度信息 + + Args: + prompt: 用户提示词 + model: 模型名称 + temperature: 随机性 + max_tokens: 最大 token 数 + system_prompt: 系统提示词(可选) + + Yields: + dict: { + "type": "chunk" | "usage", + "content": str, # 文本片段(type=chunk时) + "total_tokens": int, # 累计token数(type=chunk时) + "prompt_tokens": int, # 提示词token数(type=usage时) + "completion_tokens": int, # 生成token数(type=usage时) + } + """ + try: + messages = [] + if system_prompt: + messages.append({"role": "system", "content": system_prompt}) + messages.append({"role": "user", "content": prompt}) + + model_id = self.PRESET_MODELS.get(model, model) if model else self.default_model + + request_params = { + "model": model_id, + "messages": messages, + "temperature": temperature, + "stream": True, + } + + if max_tokens: + request_params["max_tokens"] = max_tokens + + # 流式调用 + stream = self.client.chat.completions.create(**request_params) + + total_chars = 0 + prompt_tokens = 0 + completion_tokens = 0 + + for chunk in stream: + # 获取文本内容 + if chunk.choices and chunk.choices[0].delta.content: + content = chunk.choices[0].delta.content + total_chars += len(content) + yield { + "type": "chunk", + "content": content, + "total_chars": total_chars, + } + + # 获取使用统计(最后一个chunk) + if chunk.usage: + prompt_tokens = chunk.usage.prompt_tokens + completion_tokens = chunk.usage.completion_tokens + + # 发送最终统计 + yield { + "type": "usage", + "prompt_tokens": prompt_tokens, + "completion_tokens": completion_tokens, + } + + except Exception as e: + raise ProviderError( + f"火山方舟流式生成失败: {str(e)}", provider_id=self.provider_id, original_error=e + ) + + async def generate_image( + self, prompt: str, model: str | None = None, size: str = "1024x1024", **kwargs + ) -> dict: + """ + 生成图片(Seedream 系列) + + Args: + prompt: 图片提示词 + model: 图片模型 ID + size: 图片尺寸 + + Returns: + dict: 包含图片 URL 或 base64 数据 + """ + try: + # 图片生成需要单独的图片模型,不在当前配置中 + # 如需使用,请在模型广场开通 doubao-seed-1.6 并配置 + image_model = model or "doubao-seed-1.6-flash-250828" + response = self.client.images.generate( + model=image_model, prompt=prompt, size=size, **kwargs + ) + + # 解析图片结果 + images = [] + for img in response.data: + images.append( + { + "url": img.url, + "b64_json": img.b64_json, + "revised_prompt": img.revised_prompt, + } + ) + + return { + "images": images, + "model": response.model, + } + + except Exception as e: + raise ProviderError( + f"火山方舟图片生成失败: {str(e)}", provider_id=self.provider_id, original_error=e + ) + + async def create_embeddings(self, texts: list[str], model: str | None = None, **kwargs) -> dict: + """ + 文本向量化 + + Args: + texts: 文本列表 + model: 向量化模型 + + Returns: + dict: 包含向量化结果 + """ + try: + response = self.client.embeddings.create( + model=model or "doubao-embedding-1.5", input=texts, **kwargs + ) + + embeddings = [] + for item in response.data: + embeddings.append( + { + "index": item.index, + "embedding": item.embedding, + } + ) + + return { + "embeddings": embeddings, + "model": response.model, + "usage": { + "prompt_tokens": response.usage.prompt_tokens, + "total_tokens": response.usage.total_tokens, + }, + } + + except Exception as e: + raise ProviderError( + f"火山方舟向量化失败: {str(e)}", provider_id=self.provider_id, original_error=e + ) + + async def health_check(self, model: str | None = None) -> ModelHealth: + """健康检查""" + start_time = time.time() + test_model = model or self.default_model + + try: + response = self.client.chat.completions.create( + model=test_model, + messages=[{"role": "user", "content": "Hi"}], + max_tokens=5, + ) + + response_time = (time.time() - start_time) * 1000 + + return ModelHealth( + id=test_model, + name=f"火山方舟 {test_model}", + is_available=True, + response_time=response_time, + last_error=None, + ) + + except Exception as e: + return ModelHealth( + id=test_model, + name=f"火山方舟 {test_model}", + is_available=False, + response_time=(time.time() - start_time) * 1000, + last_error=str(e), + ) + + @property + def available_models(self) -> list[str]: + """返回可用模型列表(与 ai_models.yaml 配置保持一致)""" + return [ + "doubao-seed-2-0-pro", + "deepseek-v3-2", + "doubao-seed-2-0-lite", + "doubao-lite-32k", + ] diff --git a/python-api/app/api/__init__.py b/python-api/app/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python-api/app/api/deps.py b/python-api/app/api/deps.py new file mode 100644 index 0000000..ab3c9b2 --- /dev/null +++ b/python-api/app/api/deps.py @@ -0,0 +1,83 @@ +""" +依赖注入工具 +============ +""" + +from __future__ import annotations + +from fastapi import Depends, HTTPException, status +from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.config import get_settings +from app.core.security import verify_token +from app.db.session import get_db as db_session +from app.models.user import User + +settings = get_settings() +security = HTTPBearer(auto_error=False) + + +# 数据库依赖 +async def get_db() -> AsyncSession: + """获取数据库 Session""" + async for session in db_session(): + yield session + + +async def get_current_user( + credentials: HTTPAuthorizationCredentials | None = Depends(security), + db: AsyncSession = Depends(get_db), +) -> User: + """ + 获取当前登录用户 + + 从 Authorization Header 中提取 JWT Token 并验证 + """ + if credentials is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="缺少认证信息", + headers={"WWW-Authenticate": "Bearer"}, + ) + + token = credentials.credentials + payload = verify_token(token) + + if payload is None or payload.get("sub") is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="无效的认证信息", + headers={"WWW-Authenticate": "Bearer"}, + ) + + user_id = payload.get("sub") + + result = await db.execute(select(User).where(User.id == user_id)) + user = result.scalar_one_or_none() + + if user is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="用户不存在", + headers={"WWW-Authenticate": "Bearer"}, + ) + + return user + + +async def get_current_user_optional( + credentials: HTTPAuthorizationCredentials | None = Depends(security), + db=Depends(get_db), +) -> User | None: + """ + 获取当前登录用户(可选,未登录返回 None) + """ + if credentials is None: + return None + + try: + return await get_current_user(credentials, db) + except HTTPException: + return None diff --git a/python-api/app/api/v1/__init__.py b/python-api/app/api/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python-api/app/api/v1/ai_models.py b/python-api/app/api/v1/ai_models.py new file mode 100644 index 0000000..0e07b69 --- /dev/null +++ b/python-api/app/api/v1/ai_models.py @@ -0,0 +1,552 @@ +""" +AI 模型管理与生成 API +===================== + +提供模型列表查询、文本生成、脚本生成、润色等功能。 + +模型配置存储在 config/ai_models.yaml,支持热重载。 +""" + +import logging + +logger = logging.getLogger(__name__) + +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel, Field + +from app.ai.model_router import get_model_router +from app.core.config_loader import get_config_loader +from app.schemas.common import ApiResponse, success_response +from app.services.ai_response_utils import ( + safe_parse_ai_json_response, + validate_and_normalize_shots, + validate_shots_structure, +) + +router = APIRouter() + + +# ============ 请求/响应 Schema ============ + + +class PlatformResponse(BaseModel): + """平台响应""" + + id: str + name: str + provider: str + + +class ModelResponse(BaseModel): + """模型响应""" + + id: str + platform_id: str + model_name: str + display_name: str + capabilities: list[str] + default_params: dict + is_enabled: bool + full_model_id: str + + +class GenerateRequest(BaseModel): + """生成请求""" + + prompt: str = Field(..., description="提示词") + model_id: str | None = Field(None, description="指定模型 ID") + task_type: str | None = Field( + None, description="任务类型,用于自动选模型: script/polish" + ) + temperature: float | None = Field(None, description="随机性 (0-2)") + max_tokens: int | None = Field(None, description="最大生成长度") + + +class GenerateResponse(BaseModel): + """生成响应""" + + content: str + model: str + usage: dict | None + + +class HealthResponse(BaseModel): + """健康检查响应""" + + status: str + total_models: int + available_models: int + models: list[dict] + + +# ============ API 路由 ============ + + +@router.get("/platforms", response_model=ApiResponse[list[PlatformResponse]]) +async def list_platforms(): + """获取所有平台列表""" + config_loader = get_config_loader() + platforms = config_loader.get_all_platforms() + + return success_response( + data=[ + PlatformResponse( + id=p.id, + name=p.name, + provider=p.provider, + ) + for p in platforms + ] + ) + + +@router.get("/models", response_model=ApiResponse[list[ModelResponse]]) +async def list_models(capability: str | None = None): + """获取模型列表 + + Args: + capability: 按能力过滤,如 script、polish、chat + """ + router = await get_model_router() + models = router.list_models(capability=capability) + + return success_response( + data=[ + ModelResponse( + id=m["id"], + platform_id=m["platform_id"], + model_name=m["model_name"], + display_name=m["display_name"], + capabilities=m["capabilities"], + default_params=m["default_params"], + is_enabled=True, # 列表中的都是启用的 + full_model_id=f"{m['platform_id']}/{m['id']}", + ) + for m in models + ] + ) + + +@router.post("/generate", response_model=ApiResponse[GenerateResponse]) +async def generate_text(data: GenerateRequest): + """文本生成(自动路由到对应平台)""" + router = await get_model_router() + + try: + result = await router.generate( + prompt=data.prompt, + model_id=data.model_id, + task_type=data.task_type, + temperature=data.temperature, + max_tokens=data.max_tokens, + ) + + return success_response( + data=GenerateResponse( + content=result.content, + model=result.model, + usage=result.usage, + ) + ) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/health", response_model=ApiResponse[HealthResponse]) +async def health_check(model_id: str | None = None): + """检查模型健康状态""" + router = await get_model_router() + + health_results = await router.health_check(model_id) + + models_status = [] + available_count = 0 + + for mid, health in health_results.items(): + models_status.append( + { + "id": mid, + "name": health.name, + "is_available": health.is_available, + "response_time": health.response_time, + "last_error": health.last_error, + } + ) + if health.is_available: + available_count += 1 + + return success_response( + data={ + "status": "healthy" if available_count > 0 else "unhealthy", + "total_models": len(models_status), + "available_models": available_count, + "models": models_status, + } + ) + + +@router.get("/platforms/{platform_id}/test", response_model=ApiResponse[dict]) +async def test_platform_connection(platform_id: str): + """测试平台连接""" + from app.ai.model_router import PlatformInstance + + config_loader = get_config_loader() + platform = config_loader.get_platform(platform_id) + + if not platform: + raise HTTPException(status_code=404, detail="平台不存在") + + try: + # PlatformInstance 自动从 Settings 读取 API Key + instance = PlatformInstance( + { + "id": platform.id, + "name": platform.name, + "provider": platform.provider, + "base_url": platform.base_url, + } + ) + + # 尝试调用 + result = await instance.provider.health_check() + + return success_response( + data={ + "platform_id": platform_id, + "success": result.is_available, + "response_time": result.response_time, + "message": "连接成功" if result.is_available else result.last_error, + } + ) + except Exception as e: + return success_response( + data={ + "platform_id": platform_id, + "success": False, + "error": str(e), + } + ) + + +@router.post("/reload", response_model=ApiResponse[dict]) +async def reload_config(): + """重新加载配置文件""" + router = await get_model_router() + reloaded = router.reload_config() + + if reloaded: + return success_response(data={"reloaded": True}, message="配置已重新加载") + else: + return success_response(data={"reloaded": False}, message="配置文件无变化") + + +# ============================================================================= +# Prompt 模板 API +# ============================================================================= + +from app.ai.prompts import ( + SCRIPT_TYPES, + VIDEO_STYLES, + PolishPromptBuilder, + ScriptPromptBuilder, +) + + +class PromptTemplatesResponse(BaseModel): + """Prompt 模板配置响应""" + + script_types: list[dict] + video_styles: list[dict] + tones: list[str] + + +class ScriptGenerateRequest(BaseModel): + """脚本生成请求""" + + topic: str = Field(..., description="脚本主题", example="水电改造的3个致命错误") + duration: int = Field(30, ge=15, le=120, description="视频时长(秒)") + script_type: str = Field("干货型", description="脚本类型") + video_style: str = Field("口播", description="视频风格") + tone: str | None = Field(None, description="语气风格") + requirements: str | None = Field(None, description="额外要求") + model_id: str | None = Field(None, description="指定模型ID,默认使用系统默认模型") + + +class ScriptGenerateResponse(BaseModel): + """脚本生成响应 - 针对前端展示优化""" + + success: bool + script: list[ + dict | None + ] # 镜头列表,包含 shot_number, type, scene/prompt, voiceover, duration, word_count + total_duration: int | None # 预计总时长(秒) + target_duration: int # 目标时长(秒) + total_word_count: int | None # 总字数(供前端展示) + segment_count: int | None # 分镜数量(供前端展示) + empty_shot_count: int | None # 空镜数量(供前端展示) + script_type: str + model: str + usage: dict | None + error: str | None + raw_content: str | None + + +class PolishRequest(BaseModel): + """润色请求""" + + content: str = Field(..., description="需要润色的内容") + polish_type: str = Field("voiceover", description="润色类型:scene/voiceover") + model_id: str | None = Field(None, description="指定模型ID") + + +class PolishResponse(BaseModel): + """润色响应""" + + success: bool + original: str + polished: str | None + polish_type: str + model: str + usage: dict | None + + +@router.get("/prompts/templates", response_model=ApiResponse[PromptTemplatesResponse]) +async def get_prompt_templates(): + """ + 获取所有可用的 Prompt 模板配置 + + 包括脚本类型、视频风格、语气风格等选项。 + """ + return success_response( + data={ + "script_types": [ + { + "id": key, + "name": value["name"], + "description": value["description"], + "key_points": value["key_points"], + } + for key, value in SCRIPT_TYPES.items() + if key != "default" + ], + "video_styles": [ + { + "id": key, + "name": value["name"], + "description": value["description"], + } + for key, value in VIDEO_STYLES.items() + ], + "tones": ["专业", "亲和", "幽默", "严肃", "激情"], + } + ) + + +@router.post("/prompts/build", response_model=ApiResponse[dict]) +async def build_system_prompt( + duration: int = 30, + script_type: str = "干货型", + video_style: str = "口播", + tone: str | None = None, +): + """ + 构建系统 Prompt(用于调试和预览) + + 返回构建好的系统 Prompt,可用于前端预览或调试。 + """ + builder = ScriptPromptBuilder() + prompt = builder.build( + duration=duration, + script_type=script_type, + video_style=video_style, + industry="家装", + tone=tone, + ) + + return success_response( + data={ + "system_prompt": prompt, + "length": len(prompt), + "parameters": { + "duration": duration, + "script_type": script_type, + "video_style": video_style, + "tone": tone, + }, + } + ) + + +@router.post("/scripts/generate", response_model=ApiResponse[ScriptGenerateResponse]) +async def generate_script(data: ScriptGenerateRequest): + """ + 生成家装行业短视频脚本 + + 使用专业的 Prompt 模板生成包含分镜+空镜的混合脚本。 + 针对前端展示优化,返回分镜数、空镜数、总字数等统计信息。 + """ + router = await get_model_router() + + # 构建系统 Prompt + builder = ScriptPromptBuilder() + system_prompt = builder.build( + duration=data.duration, + script_type=data.script_type, + video_style=data.video_style, + industry="家装", + tone=data.requirements, + custom_requirements=data.requirements, + ) + + # 构建用户输入 + user_prompt = f"""主题是"{data.topic}" + +要求: +1. 严格按照时长要求控制 +2. 每个镜头的配音字数必须匹配时长(4-5字/秒) +3. 空镜必须有画外音,不能为空 +4. 只返回JSON数组,不要有其他文字""" + + full_prompt = f"{system_prompt}\n\n【用户输入】\n{user_prompt}\n\n请生成脚本,只返回JSON数组:" + + # 调用模型 + try: + result = await router.generate( + prompt=full_prompt, + model_id=data.model_id, + task_type="script", + temperature=0.7, + max_tokens=2500, + ) + + # 安全地解析 JSON 响应 + success_parsed, parsed_data, error_msg = safe_parse_ai_json_response( + result.content + ) + + if not success_parsed: + logger.error(f"AI 响应解析失败: {error_msg}") + return success_response( + data={ + "success": False, + "script": None, + "total_duration": None, + "target_duration": data.duration, + "total_word_count": None, + "segment_count": None, + "empty_shot_count": None, + "script_type": data.script_type, + "model": result.model, + "usage": result.usage, + "error": error_msg or "JSON解析失败", + "raw_content": result.content, + } + ) + + # 验证并标准化分镜数据 + try: + script = validate_and_normalize_shots(parsed_data) + except Exception as e: + logger.error(f"分镜数据标准化失败: {e}") + return success_response( + data={ + "success": False, + "script": None, + "total_duration": None, + "target_duration": data.duration, + "total_word_count": None, + "segment_count": None, + "empty_shot_count": None, + "script_type": data.script_type, + "model": result.model, + "usage": result.usage, + "error": f"分镜数据格式错误: {e}", + "raw_content": result.content, + } + ) + + # 验证分镜结构 + is_valid, validation_errors = validate_shots_structure(script) + if not is_valid: + logger.warning(f"分镜结构验证失败: {validation_errors}") + # 继续处理,但记录警告 + + # 计算统计信息(供前端展示) + total_duration = sum( + int(shot.get("duration", "5s").rstrip("s秒")) + for shot in script + if isinstance(shot, dict) + ) + total_word_count = sum( + len(shot.get("voiceover", "")) for shot in script if isinstance(shot, dict) + ) + segment_count = sum( + 1 + for shot in script + if isinstance(shot, dict) and shot.get("type") == "segment" + ) + empty_shot_count = sum( + 1 + for shot in script + if isinstance(shot, dict) and shot.get("type") == "empty_shot" + ) + + return success_response( + data={ + "success": True, + "script": script, + "total_duration": total_duration, + "target_duration": data.duration, + "total_word_count": total_word_count, + "segment_count": segment_count, + "empty_shot_count": empty_shot_count, + "script_type": data.script_type, + "model": result.model, + "usage": result.usage, + "error": None, + "raw_content": None, + } + ) + + except Exception as e: + raise HTTPException(status_code=500, detail=f"生成失败: {str(e)}") + + +@router.post("/scripts/polish", response_model=ApiResponse[PolishResponse]) +async def polish_script_content(data: PolishRequest): + """ + 润色脚本内容 + + 对场景描述或口播文案进行专业润色。 + """ + router = await get_model_router() + + # 构建润色 Prompt + builder = PolishPromptBuilder() + system_prompt = builder.build(data.polish_type) + + full_prompt = f"{system_prompt}\n\n【待润色内容】\n{data.content}\n\n请润色:" + + # 调用模型 + try: + result = await router.generate( + prompt=full_prompt, + model_id=data.model_id, + task_type="polish", + temperature=0.6, + max_tokens=1000, + ) + + return success_response( + data={ + "success": True, + "original": data.content, + "polished": result.content, + "polish_type": data.polish_type, + "model": result.model, + "usage": result.usage, + } + ) + + except Exception as e: + raise HTTPException(status_code=500, detail=f"润色失败: {str(e)}") diff --git a/python-api/app/api/v1/auth.py b/python-api/app/api/v1/auth.py new file mode 100644 index 0000000..eefdd29 --- /dev/null +++ b/python-api/app/api/v1/auth.py @@ -0,0 +1,83 @@ +""" +认证模块 API +============ + +采用"手机号 + JWT"的认证方案。 +""" + +from __future__ import annotations + +from fastapi import APIRouter, Depends + +from app.api.deps import get_current_user +from app.core.security import create_access_token +from app.crud.user import user as user_crud +from app.db.session import AsyncSession, get_db +from app.models.user import User +from app.schemas.auth import LoginResponse, MobileLoginRequest +from app.schemas.common import ApiResponse, success_response + +router = APIRouter() + + +@router.post("/login", response_model=ApiResponse[LoginResponse]) +async def login( + request: MobileLoginRequest, + db: AsyncSession = Depends(get_db), +): + """ + 手机号登录/注册 + + - 如果手机号已存在,返回对应用户 + - 如果不存在,自动创建新用户 + - 返回 JWT Token 用于后续认证 + """ + # 获取或创建用户 + user_obj = await user_crud.get_or_create_by_mobile( + db, + mobile=request.mobile, + nickname=request.nickname, + ) + + # 生成 JWT Token + token = create_access_token(data={"sub": user_obj.id, "mobile": user_obj.mobile}) + + return success_response( + data=LoginResponse( + token=token, + user={ + "id": user_obj.id, + "nickname": user_obj.nickname or "", + "avatar": user_obj.avatar_url or "", + }, + ), + message="登录成功", + ) + + +@router.get("/me", response_model=ApiResponse[dict]) +async def get_me( + current_user: User = Depends(get_current_user), +): + """获取当前登录用户信息""" + return success_response( + data={ + "id": current_user.id, + "mobile": current_user.mobile, + "nickname": current_user.nickname, + "avatar": current_user.avatar_url, + "createdAt": current_user.created_at.isoformat() if current_user.created_at else None, + } + ) + + +@router.post("/refresh", response_model=ApiResponse[dict]) +async def refresh_token( + current_user: User = Depends(get_current_user), +): + """刷新 JWT Token""" + new_token = create_access_token(data={"sub": current_user.id, "mobile": current_user.mobile}) + return success_response( + data={"token": new_token}, + message="Token 刷新成功", + ) diff --git a/python-api/app/api/v1/avatar.py b/python-api/app/api/v1/avatar.py new file mode 100644 index 0000000..b7f1f28 --- /dev/null +++ b/python-api/app/api/v1/avatar.py @@ -0,0 +1,560 @@ +""" +Avatar 形象克隆模块 +================== + +串行流程: +1. 使用上传的视频创建 KlingAI 自定义音色 (custom-voices) +2. 轮询等待音色生成完成,获取 voice_id +3. 使用同一视频 + voice_id 创建 KlingAI 主体 (advanced-custom-elements) +4. 轮询等待主体生成完成,获取 provider_element_id +5. 返回统一的 AvatarItem + +异步架构: +- POST /avatar/clone 只负责注册到 Async Engine(纯 Redis,无 DB),立即返回 task_id +- 真正的轮询由 Async Engine Scheduler 在后台执行 +- 前端通过 SSE 或轮询 GET /avatar/tasks/{task_id} 查询进度 + +数据策略: +- 形象克隆数据只保存在前端本地,后端不持久化到数据库 +- 任务运行时的中间状态全部存储在 Redis 中(TTL 24h) + +错误提示策略: +- custom-voice 失败:提示"有声的人物视频"相关原因 +- element 失败:提示视频内容/质量不符合主体创建要求 +- 超时:标记为 timeout,支持重试 +""" + +import asyncio +import contextlib +import json +import logging +import uuid +from datetime import UTC, datetime + +from fastapi import APIRouter, Depends, HTTPException, Query +from fastapi.responses import StreamingResponse +from pydantic import BaseModel, ConfigDict, Field + +from app.ai.providers.klingai_provider import KlingAIProvider +from app.api.deps import get_current_user +from app.config import get_settings +from app.core.redis_client import get_redis_client +from app.scheduler.registry import JobRegistry +from app.schemas.common import ApiResponse, success_response +from app.schemas.enums import AvatarCloneStatus + +logger = logging.getLogger(__name__) +router = APIRouter() + + +def _get_kling_provider() -> KlingAIProvider: + settings = get_settings() + return KlingAIProvider( + config={ + "access_key": settings.KLINGAI_ACCESS_KEY or "", + "secret_key": settings.KLINGAI_SECRET_KEY or "", + } + ) + + +async def _get_avatar_state(redis, job_id: str) -> dict | None: + """从 Redis 读取 avatar 任务完整状态""" + data = await redis.hgetall(f"job:{job_id}") + if not data: + return None + + # 解析 JSON 字段 + for key in ("result", "params"): + if key in data and data[key]: + with contextlib.suppress(json.JSONDecodeError): + data[key] = json.loads(data[key]) + return data + + +class CloneAvatarRequest(BaseModel): + """创建形象克隆请求""" + + name: str = Field(..., min_length=1, max_length=20, description="形象名称") + video_url: str = Field(description="人物视频 URL") + + +class CloneAvatarResponse(BaseModel): + """创建形象克隆响应""" + + task_id: str = Field(..., description="任务 ID(用于 SSE/轮询跟踪进度)") + status: str = Field("pending", description="初始状态") + + +class AvatarTaskStatusResponse(BaseModel): + """任务状态查询响应""" + + task_id: str + status: str = Field(..., description="当前状态") + fail_reason: str | None = Field(None, description="失败原因") + voice_id: str | None = Field(None, description="已生成的音色 ID") + human_id: int | None = Field(None, description="已生成的主体 ID") + trial_url: str | None = Field(None, description="试听 URL") + video_url: str = Field(..., description="原始视频 URL") + name: str = Field(..., description="形象名称") + created_at: datetime = Field(..., description="创建时间") + updated_at: datetime = Field(..., description="更新时间") + + +class AvatarItem(BaseModel): + """形象库列表项""" + + model_config = ConfigDict(from_attributes=True) + + id: str = Field(..., description="形象唯一标识") + name: str = Field(..., description="展示名称") + voice_id: str = Field(..., description="Kling 自定义音色 ID") + human_id: int = Field(..., description="数字人主体 ID") + video_url: str = Field(description="原始人物视频 URL") + trial_url: str | None = Field(None, description="音色试听 URL") + record_time: str = Field(description="创建时间 ISO 字符串") + + +class UpdateAvatarNameRequest(BaseModel): + """更新形象名称请求""" + + name: str = Field(..., min_length=1, max_length=20, description="新形象名称") + + +# ============================================================ +# API 路由 +# ============================================================ + + +@router.post("/avatar/clone", response_model=ApiResponse[CloneAvatarResponse]) +async def clone_avatar( + data: CloneAvatarRequest, + current_user: dict = Depends(get_current_user), +): + """ + 提交形象克隆任务 + + 立即返回 task_id,前端通过 SSE 或轮询跟踪进度。 + 实际串行流程由 Async Engine Scheduler 异步执行。 + 任务状态纯 Redis 存储,不写入数据库。 + """ + user_id = str(current_user.id) + name = data.name.strip() + video_url = data.video_url.strip() + + # 生成 task_id + task_id = f"avt_{uuid.uuid4().hex[:16]}" + now = datetime.now(UTC) + + # 写入 Redis,供 Async Engine 调度(同时存储 avatar 初始状态) + redis = get_redis_client() + registry = JobRegistry(redis) + await registry.create(task_id, "avatar_clone", user_id) + await registry.update( + task_id, + status="running", + progress=5, + message="开始形象克隆...", + completed=0, + total=1, + params={ + "avatar_id": task_id, + "name": name, + "video_url": video_url, + "user_id": user_id, + }, + # 存储 avatar 状态字段(供 API 查询) + avatar_status=AvatarCloneStatus.PENDING.value, + avatar_name=name, + avatar_video_url=video_url, + voice_id="", + provider_element_id="", + provider_voice_job_id="", + provider_element_job_id="", + trial_url="", + fail_reason="", + created_at=now.isoformat(), + updated_at=now.isoformat(), + ) + await registry.add_running(task_id) + + return success_response(data=CloneAvatarResponse(task_id=task_id, status="pending")) + + +@router.get("/avatar/tasks/{task_id}", response_model=ApiResponse[AvatarTaskStatusResponse]) +async def get_avatar_task_status( + task_id: str, + current_user: dict = Depends(get_current_user), +): + """查询形象克隆任务状态(从 Redis 读取)""" + redis = get_redis_client() + state = await _get_avatar_state(redis, task_id) + if not state: + raise HTTPException(status_code=404, detail="任务不存在") + + # 权限检查 + params = state.get("params", {}) if isinstance(state.get("params"), dict) else {} + if params.get("user_id") != str(current_user.id): + raise HTTPException(status_code=404, detail="任务不存在") + + def _dt(key: str) -> datetime: + raw = state.get(key, "") + if raw: + try: + return datetime.fromisoformat(raw) + except ValueError: + pass + return datetime.now(UTC) + + def _int(key: str) -> int | None: + raw = state.get(key, "") + if raw: + try: + return int(raw) + except ValueError: + pass + return None + + return success_response( + data=AvatarTaskStatusResponse( + task_id=task_id, + status=state.get("avatar_status", state.get("status", "unknown")), + fail_reason=state.get("fail_reason") or None, + voice_id=state.get("voice_id") or None, + human_id=_int("provider_element_id"), + trial_url=state.get("trial_url") or None, + video_url=params.get("video_url", ""), + name=params.get("name", ""), + created_at=_dt("created_at"), + updated_at=_dt("updated_at"), + ) + ) + + +@router.get("/avatar/clone/stream") +async def sse_avatar_clone( + task_id: str = Query(..., alias="task_id", description="任务 ID"), + current_user: dict = Depends(get_current_user), +): + """ + SSE 流:实时推送形象克隆任务状态 + + 前端连接后,每 3 秒推送一次状态,直到任务结束(succeed / failed / timeout)。 + """ + user_id = str(current_user.id) + + async def event_stream(): + for _ in range(400): # 最多 20 分钟(400 * 3s) + redis = get_redis_client() + state = await _get_avatar_state(redis, task_id) + + if not state: + payload = json.dumps( + {"status": "error", "fail_reason": "任务不存在或无权限"}, ensure_ascii=False + ) + yield f"event: error\ndata: {payload}\n\n" + break + + # 权限检查 + params = state.get("params", {}) if isinstance(state.get("params"), dict) else {} + if params.get("user_id") != user_id: + payload = json.dumps( + {"status": "error", "fail_reason": "任务不存在或无权限"}, ensure_ascii=False + ) + yield f"event: error\ndata: {payload}\n\n" + break + + avatar_status = state.get("avatar_status", state.get("status", "unknown")) + + payload = json.dumps( + { + "task_id": task_id, + "status": avatar_status, + "fail_reason": state.get("fail_reason") or None, + "voice_id": state.get("voice_id") or None, + "provider_element_id": state.get("provider_element_id") or None, + "trial_url": state.get("trial_url") or None, + "video_url": params.get("video_url", ""), + "name": params.get("name", ""), + "created_at": state.get("created_at", ""), + "updated_at": state.get("updated_at", ""), + }, + ensure_ascii=False, + ) + yield f"data: {payload}\n\n" + + if avatar_status in ( + AvatarCloneStatus.SUCCEED, + AvatarCloneStatus.VOICE_FAILED, + AvatarCloneStatus.ELEMENT_FAILED, + AvatarCloneStatus.TIMEOUT, + ): + break + + await asyncio.sleep(3) + else: + # 达到最大轮询次数,推送超时事件 + payload = json.dumps( + {"status": "timeout", "fail_reason": "连接超时,请通过轮询接口继续跟踪"}, + ensure_ascii=False, + ) + yield f"event: timeout\ndata: {payload}\n\n" + + return StreamingResponse( + event_stream(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + }, + ) + + +@router.post("/avatar/tasks/{task_id}/retry", response_model=ApiResponse[dict]) +async def retry_avatar_task( + task_id: str, + current_user: dict = Depends(get_current_user), +): + """ + 重试失败或超时的形象克隆任务 + + 仅允许对 voice_failed / element_failed / timeout 状态的任务重试。 + 重试时会重置状态为 pending 并重新注册到 Async Engine。 + """ + redis = get_redis_client() + state = await _get_avatar_state(redis, task_id) + if not state: + raise HTTPException(status_code=404, detail="任务不存在") + + params = state.get("params", {}) if isinstance(state.get("params"), dict) else {} + if params.get("user_id") != str(current_user.id): + raise HTTPException(status_code=404, detail="任务不存在") + + avatar_status = state.get("avatar_status", state.get("status", "")) + if avatar_status not in ( + AvatarCloneStatus.VOICE_FAILED.value, + AvatarCloneStatus.ELEMENT_FAILED.value, + AvatarCloneStatus.TIMEOUT.value, + ): + raise HTTPException(status_code=400, detail=f"当前状态 {avatar_status} 不支持重试") + + # 重置状态 + registry = JobRegistry(redis) + now = datetime.now(UTC).isoformat() + await registry.update( + task_id, + status="running", + avatar_status=AvatarCloneStatus.PENDING, + fail_reason="", + voice_id="", + provider_element_id="", + provider_voice_job_id="", + provider_element_job_id="", + trial_url="", + updated_at=now, + ) + await registry.add_running(task_id) + + return success_response(data={"task_id": task_id, "status": "pending"}) + + +@router.delete("/avatar/{avatar_id}", response_model=ApiResponse[dict]) +async def delete_avatar( + avatar_id: str, + voice_id: str | None = None, + current_user: dict = Depends(get_current_user), +): + """ + 删除形象:清理 Kling 资源 + 删除 Redis 任务记录 + + 不操作数据库,形象数据由前端本地管理。 + """ + redis = get_redis_client() + state = await _get_avatar_state(redis, avatar_id) + + # 获取 Kling 资源 ID(优先用传入的,否则从 Redis 读) + actual_voice_id = voice_id + actual_element_id = None + if state: + params = state.get("params", {}) if isinstance(state.get("params"), dict) else {} + if params.get("user_id") == str(current_user.id): + actual_element_id = state.get("provider_element_id") + if not actual_voice_id: + actual_voice_id = state.get("voice_id") + + # 异步清理 Kling 资源(不阻塞前端) + provider = _get_kling_provider() + if actual_element_id: + try: + await provider.delete_element(str(actual_element_id)) + except Exception as e: + logger.warning(f"delete_element failed: {e}") + + if actual_voice_id: + try: + await provider.delete_custom_voice(actual_voice_id) + except Exception as e: + logger.warning(f"delete_custom_voice failed: {e}") + + # 删除 Redis 任务记录 + registry = JobRegistry(redis) + await registry.delete(avatar_id) + + return success_response(data={"success": True, "message": "形象已删除"}) + + +@router.get("/avatar/library", response_model=ApiResponse[list[AvatarItem]]) +async def get_avatar_library( + current_user: dict = Depends(get_current_user), +): + """ + 获取当前用户的克隆形象库 + + 形象数据只保存在前端本地,后端不持久化。 + 此接口始终返回空列表,由前端从 localStorage/文件系统读取真实数据。 + """ + return success_response(data=[]) + + +@router.patch("/avatar/{avatar_id}", response_model=ApiResponse[dict]) +async def update_avatar_name( + avatar_id: str, + data: UpdateAvatarNameRequest, + current_user: dict = Depends(get_current_user), +): + """ + 更新形象名称 + + 形象数据由前端本地管理,后端仅返回成功。 + """ + new_name = data.name.strip() + if not new_name: + raise HTTPException(status_code=400, detail="名称不能为空") + + return success_response(data={"success": True, "name": new_name}) + + +# ============================================================================= +# 管理和监控接口(用于排查问题和手动恢复) +# ============================================================================= + + +class AvatarHealthResponse(BaseModel): + """形象克隆服务健康状态""" + + total_processing: int = Field(..., description="处理中的任务总数") + pending: int = Field(..., description="待处理任务数") + voice_processing: int = Field(..., description="音色生成中任务数") + element_processing: int = Field(..., description="主体生成中任务数") + stuck_tasks: int = Field(..., description="卡住任务数(超过30分钟)") + recent_failures: int = Field(..., description="最近1小时失败数") + + +@router.get("/avatar/health", response_model=ApiResponse[AvatarHealthResponse]) +async def get_avatar_health( + current_user: dict = Depends(get_current_user), +): + """ + 获取形象克隆服务健康状态 + + 基于 Redis 运行中任务统计,不查询数据库。 + """ + redis = get_redis_client() + registry = JobRegistry(redis) + job_ids = await registry.get_running_job_ids() + + total_processing = 0 + pending = 0 + voice_processing = 0 + element_processing = 0 + stuck_tasks = 0 + recent_failures = 0 + + now = datetime.now(UTC) + stuck_threshold = now.timestamp() - 30 * 60 # 30 分钟前 + recent_threshold = now.timestamp() - 60 * 60 # 1 小时前 + + for job_id in job_ids: + state = await _get_avatar_state(redis, job_id) + if not state: + continue + + # 只统计当前用户的任务(非管理员) + params = state.get("params", {}) if isinstance(state.get("params"), dict) else {} + if params.get("user_id") != str(current_user.id): + continue + + job_type = state.get("type", "") + if job_type != "avatar_clone": + continue + + avatar_status = state.get("avatar_status", state.get("status", "")) + total_processing += 1 + + if avatar_status == AvatarCloneStatus.PENDING.value: + pending += 1 + elif avatar_status == AvatarCloneStatus.VOICE_PROCESSING.value: + voice_processing += 1 + elif avatar_status == AvatarCloneStatus.ELEMENT_PROCESSING.value: + element_processing += 1 + + # 检查是否卡住(updated_at 超过 30 分钟) + updated_at_raw = state.get("updated_at", "") + if updated_at_raw: + try: + updated_ts = datetime.fromisoformat(updated_at_raw).timestamp() + if updated_ts < stuck_threshold and avatar_status in ( + AvatarCloneStatus.PENDING.value, + AvatarCloneStatus.VOICE_PROCESSING.value, + AvatarCloneStatus.ELEMENT_PROCESSING.value, + ): + stuck_tasks += 1 + except ValueError: + pass + + # 检查最近失败 + if avatar_status in ( + AvatarCloneStatus.VOICE_FAILED.value, + AvatarCloneStatus.ELEMENT_FAILED.value, + AvatarCloneStatus.TIMEOUT.value, + ): + updated_at_raw = state.get("updated_at", "") + if updated_at_raw: + try: + updated_ts = datetime.fromisoformat(updated_at_raw).timestamp() + if updated_ts >= recent_threshold: + recent_failures += 1 + except ValueError: + pass + + return success_response( + data=AvatarHealthResponse( + total_processing=total_processing, + pending=pending, + voice_processing=voice_processing, + element_processing=element_processing, + stuck_tasks=stuck_tasks, + recent_failures=recent_failures, + ) + ) + + +@router.post("/avatar/admin/trigger-recovery", response_model=ApiResponse[dict]) +async def admin_trigger_recovery( + current_user: dict = Depends(get_current_user), +): + """ + 手动触发卡住任务恢复(管理员接口) + + Async Engine 会自动轮询,无需手动触发恢复。 + """ + # 权限检查:基于特定手机号判断管理员 + is_admin = current_user.mobile in ["13800138000", "admin"] + if not is_admin: + raise HTTPException(status_code=403, detail="需要管理员权限") + + return success_response( + data={ + "message": "Async Engine 会持续自动轮询,无需手动触发恢复", + "task_id": None, + } + ) diff --git a/python-api/app/api/v1/caption.py b/python-api/app/api/v1/caption.py new file mode 100644 index 0000000..8acbaf7 --- /dev/null +++ b/python-api/app/api/v1/caption.py @@ -0,0 +1,374 @@ +""" +火山引擎音视频字幕 API 路由 +============================ + +提供字幕生成、自动打轴等功能。 +""" + +import logging + +from fastapi import APIRouter, HTTPException + +from app.schemas.caption import ( + AutoAlignResult, + AutoAlignSubmitRequest, + CaptionResult, + CaptionSubmitRequest, + CaptionTaskResponse, + SrtSubtitleResponse, +) +from app.schemas.common import ApiResponse, success_response +from app.services.volcengine_caption_service import ( + VolcengineCaptionError, + VolcengineCaptionService, + get_caption_service, +) + +logger = logging.getLogger(__name__) +router = APIRouter(prefix="/caption", tags=["Caption"]) + + +@router.post("/submit", response_model=ApiResponse[CaptionTaskResponse]) +async def submit_caption_task(request: CaptionSubmitRequest): + """ + 提交字幕生成任务 + + 提交音频/视频文件URL,生成带时间轴的字幕。 + """ + try: + service = await get_caption_service() + task_id = await service.submit_caption_task( + audio_url=request.audio_url, + language=request.language, + caption_type=request.caption_type, + use_punc=request.use_punc, + use_itn=request.use_itn, + words_per_line=request.words_per_line, + max_lines=request.max_lines, + ) + + return success_response( + data=CaptionTaskResponse( + task_id=task_id, + status="pending", + ), + message="字幕任务已提交", + ) + + except VolcengineCaptionError as e: + logger.error(f"提交字幕任务失败: {e}") + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + logger.error(f"提交字幕任务异常: {e}") + raise HTTPException(status_code=500, detail=f"提交失败: {str(e)}") + + +@router.get("/query/{task_id}", response_model=ApiResponse[CaptionResult]) +async def query_caption_task(task_id: str, blocking: bool = True): + """ + 查询字幕任务结果 + + Args: + task_id: 任务ID + blocking: 是否阻塞等待结果 (默认True) + """ + try: + service = await get_caption_service() + result = await service.query_caption_task(task_id, blocking=blocking) + + return success_response(data=result) + + except VolcengineCaptionError as e: + logger.error(f"查询字幕任务失败: {e}") + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + logger.error(f"查询字幕任务异常: {e}") + raise HTTPException(status_code=500, detail=f"查询失败: {str(e)}") + + +@router.post("/generate", response_model=ApiResponse[CaptionResult]) +async def generate_caption(request: CaptionSubmitRequest, max_wait_time: int = 120): + """ + 生成字幕(完整流程) + + 提交任务并轮询结果,直接返回最终字幕数据。 + 适用于不需要异步处理的场景。 + """ + try: + service = await get_caption_service() + result = await service.generate_caption( + audio_url=request.audio_url, + language=request.language, + caption_type=request.caption_type, + use_punc=request.use_punc, + use_itn=request.use_itn, + words_per_line=request.words_per_line, + max_lines=request.max_lines, + max_wait_time=max_wait_time, + ) + + return success_response(data=result) + + except VolcengineCaptionError as e: + logger.error(f"生成字幕失败: {e}") + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + logger.error(f"生成字幕异常: {e}") + raise HTTPException(status_code=500, detail=f"生成失败: {str(e)}") + + +@router.post("/generate-ass", response_model=ApiResponse[dict]) +async def generate_ass( + request: CaptionSubmitRequest, + video_width: int = 1080, + video_height: int = 1920, + max_wait_time: int = 120, +): + """ + 生成 ASS 格式字幕(完整流程,使用抖音美好体) + + Args: + video_width: 视频宽度(默认 1080) + video_height: 视频高度(默认 1920) + """ + try: + service = await get_caption_service() + result = await service.generate_caption( + audio_url=request.audio_url, + language=request.language, + caption_type=request.caption_type, + use_punc=request.use_punc, + use_itn=request.use_itn, + words_per_line=request.words_per_line, + max_lines=request.max_lines, + max_wait_time=max_wait_time, + ) + + ass_content = service.to_ass( + result.utterances, + video_width=video_width, + video_height=video_height, + ) + + return success_response( + data={ + "ass_content": ass_content, + "utterances": result.utterances, + "duration": result.duration, + "font": "DouyinSansBold", + } + ) + + except Exception as e: + logger.error(f"生成ASS字幕失败: {e}") + raise HTTPException(status_code=500, detail=f"生成失败: {str(e)}") + + +@router.post("/generate-srt", response_model=ApiResponse[SrtSubtitleResponse]) +async def generate_srt(request: CaptionSubmitRequest, max_wait_time: int = 120): + """ + 生成 SRT 格式字幕(完整流程) + + 直接返回 SRT 格式字幕文件内容。 + """ + try: + service = await get_caption_service() + result = await service.generate_caption( + audio_url=request.audio_url, + language=request.language, + caption_type=request.caption_type, + use_punc=request.use_punc, + use_itn=request.use_itn, + words_per_line=request.words_per_line, + max_lines=request.max_lines, + max_wait_time=max_wait_time, + ) + + srt_content = service.to_srt(result.utterances) + + return success_response( + data=SrtSubtitleResponse( + srt_content=srt_content, + utterances=result.utterances, + ) + ) + + except VolcengineCaptionError as e: + logger.error(f"生成SRT字幕失败: {e}") + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + logger.error(f"生成SRT字幕异常: {e}") + raise HTTPException(status_code=500, detail=f"生成失败: {str(e)}") + + +@router.post("/ata/submit", response_model=ApiResponse[CaptionTaskResponse]) +async def submit_auto_align_task(request: AutoAlignSubmitRequest): + """ + 提交自动字幕打轴任务 + + 为已有字幕文本自动配上时间轴。 + """ + try: + service = await get_caption_service() + task_id = await service.submit_auto_align_task( + audio_url=request.audio_url, + audio_text=request.audio_text, + caption_type=request.caption_type, + sta_punc_mode=request.sta_punc_mode, + ) + + return success_response( + data=CaptionTaskResponse( + task_id=task_id, + status="pending", + ), + message="打轴任务已提交", + ) + + except VolcengineCaptionError as e: + logger.error(f"提交打轴任务失败: {e}") + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + logger.error(f"提交打轴任务异常: {e}") + raise HTTPException(status_code=500, detail=f"提交失败: {str(e)}") + + +@router.get("/ata/query/{task_id}", response_model=ApiResponse[AutoAlignResult]) +async def query_auto_align_task(task_id: str, blocking: bool = True): + """ + 查询打轴任务结果 + """ + try: + service = await get_caption_service() + result = await service.query_auto_align_task(task_id, blocking=blocking) + + return success_response(data=result) + + except VolcengineCaptionError as e: + logger.error(f"查询打轴任务失败: {e}") + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + logger.error(f"查询打轴任务异常: {e}") + raise HTTPException(status_code=500, detail=f"查询失败: {str(e)}") + + +@router.post("/ata/align") +async def auto_align_caption(request: AutoAlignSubmitRequest, max_wait_time: int = 120): + """ + 自动字幕打轴(完整流程) + + 提交打轴任务并轮询结果,直接返回最终数据。 + """ + try: + logger.info(f"[Caption API] Auto align request: audio_url={request.audio_url[:50]}...") + service = await get_caption_service() + result = await service.auto_align_caption( + audio_url=request.audio_url, + audio_text=request.audio_text, + caption_type=request.caption_type, + sta_punc_mode=request.sta_punc_mode, + max_wait_time=max_wait_time, + ) + logger.info( + f"[Caption API] Auto align result: utterances_count={len(result.utterances) if result.utterances else 0}" + ) + if result.utterances: + logger.info(f"[Caption API] First utterance: {result.utterances[0]}") + + # 手动序列化为字典,确保嵌套模型正确处理 + response_data = { + "code": 0, + "message": "Success", + "duration": result.duration, + "utterances": [ + { + "text": u.text, + "start_time": u.start_time, + "end_time": u.end_time, + } + for u in (result.utterances or []) + ], + } + logger.info(f"[Caption API] Response data: {response_data}") + return success_response(data=response_data) + + except VolcengineCaptionError as e: + logger.error(f"自动打轴失败: {e}") + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + logger.error(f"自动打轴异常: {e}") + raise HTTPException(status_code=500, detail=f"打轴失败: {str(e)}") + + +@router.post("/convert/ass", response_model=ApiResponse[dict]) +async def convert_to_ass( + result: CaptionResult, + video_width: int = 1080, + video_height: int = 1920, +): + """ + 将字幕结果转换为 ASS 格式(使用抖音美好体) + """ + try: + service = VolcengineCaptionService("", "") # 不需要认证 + ass_content = service.to_ass( + result.utterances, + video_width=video_width, + video_height=video_height, + ) + + return success_response( + data={ + "ass_content": ass_content, + "font": "DouyinSansBold", + "utterances_count": len(result.utterances), + } + ) + + except Exception as e: + logger.error(f"转换ASS失败: {e}") + raise HTTPException(status_code=500, detail=f"转换失败: {str(e)}") + + +@router.post("/convert/srt", response_model=ApiResponse[dict]) +async def convert_to_srt(result: CaptionResult): + """ + 将字幕结果转换为 SRT 格式 + + 用于将 /generate 返回的原始数据转换为 SRT 格式。 + """ + try: + service = VolcengineCaptionService("", "") # 不需要认证 + srt_content = service.to_srt(result.utterances) + + return success_response( + data={ + "srt_content": srt_content, + "utterances_count": len(result.utterances), + } + ) + + except Exception as e: + logger.error(f"转换SRT失败: {e}") + raise HTTPException(status_code=500, detail=f"转换失败: {str(e)}") + + +@router.post("/convert/vtt", response_model=ApiResponse[dict]) +async def convert_to_vtt(result: CaptionResult): + """ + 将字幕结果转换为 WebVTT 格式 + """ + try: + service = VolcengineCaptionService("", "") # 不需要认证 + vtt_content = service.to_vtt(result.utterances) + + return success_response( + data={ + "vtt_content": vtt_content, + "utterances_count": len(result.utterances), + } + ) + + except Exception as e: + logger.error(f"转换VTT失败: {e}") + raise HTTPException(status_code=500, detail=f"转换失败: {str(e)}") diff --git a/python-api/app/api/v1/klingai.py b/python-api/app/api/v1/klingai.py new file mode 100644 index 0000000..a0cfc8b --- /dev/null +++ b/python-api/app/api/v1/klingai.py @@ -0,0 +1,1046 @@ +""" +KlingAI (可灵 AI) API 路由 +========================== + +提供视频生成、图像生成、对口型等功能。 +""" + +import logging +from typing import Any + +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel, Field + +from app.ai.providers.klingai_provider import KlingAIProvider +from app.config import get_settings +from app.core.config_loader import get_config_loader +from app.schemas.common import ApiResponse, success_response + +logger = logging.getLogger(__name__) +router = APIRouter(prefix="/klingai", tags=["KlingAI"]) + + +# ============ 请求/响应 Schema ============ + + +class OmniVideoRequest(BaseModel): + """Omni-Video 视频生成请求(kling-v3-omni / kling-video-o1)""" + + prompt: str = Field( + ..., + description="视频描述提示词(不超过2500字符),支持 <<>>/<<>>/<<>> 引用语法", + example="一只<<>>在花园里奔跑,阳光明媚", + ) + model: str | None = Field("kling-v3-omni", description="模型: kling-v3-omni, kling-video-o1") + duration: int = Field(5, ge=3, le=15, description="视频时长(秒): 3/5/10/15,Omni支持3-15秒") + aspect_ratio: str = Field("16:9", description="宽高比: 16:9, 9:16, 1:1") + mode: str = Field("pro", description="生成模式: pro(高质量) 或 std(标准)") + sound: str = Field("on", description="声音控制: on=音画同出, off=无声") + negative_prompt: str | None = Field(None, description="负面提示词") + # 多镜头参数 + multi_shot: bool = Field(False, description="是否启用多镜头模式") + shot_type: str | None = Field( + None, description="分镜方式: customize=自定义, intelligence=智能分镜" + ) + multi_prompt: list[dict | None] = Field( + None, + description="多镜头提示词列表,每个元素包含 index, prompt, duration,最多6个分镜", + ) + # 参考资源 + image_list: list[dict | None] = Field(None, description="参考图片列表,最多4张") + element_list: list[dict | None] = Field( + None, description="主体参考列表,格式: [{'elementId': 123}], 最多7个" + ) + video_list: list[dict | None] = Field(None, description="参考视频列表") + callback_url: str | None = Field(None, description="回调通知地址") + external_task_id: str | None = Field(None, description="自定义任务ID") + + +class VideoGenerateRequest(BaseModel): + """文生视频请求""" + + prompt: str = Field( + ..., + description="视频描述提示词(不超过2500字符)", + example="一只猫在花园里玩耍", + ) + model: str | None = Field("kling-v2.6", description="视频模型: kling-v2.6, kling-v2.5-turbo") + duration: int = Field(5, ge=5, le=10, description="视频时长(秒): 5 或 10") + aspect_ratio: str = Field("16:9", description="宽高比: 16:9, 9:16, 1:1, 4:3, 3:4") + mode: str = Field("pro", description="生成模式: pro(高质量) 或 std(标准)") + negative_prompt: str | None = Field(None, description="负面提示词") + callback_url: str | None = Field(None, description="回调通知地址") + + +class VideoGenerateResponse(BaseModel): + """视频生成响应""" + + task_id: str + task_status: str + created_at: int + updated_at: int + + +class Image2VideoRequest(BaseModel): + """图生视频请求""" + + image_url: str = Field(..., description="输入图片 URL") + prompt: str | None = Field(None, description="视频运动描述提示词") + model: str | None = Field("kling-v2.6", description="视频模型") + duration: int = Field(5, ge=5, le=10, description="视频时长(秒)") + aspect_ratio: str | None = Field(None, description="宽高比") + mode: str = Field("pro", description="生成模式") + callback_url: str | None = Field(None, description="回调通知地址") + + +class IdentifyFaceRequest(BaseModel): + """人脸识别请求""" + + video_id: str | None = Field(None, description="KlingAI 生成的视频 ID") + video_url: str | None = Field(None, description="上传的视频 URL(与 videoId 二选一") + + +class IdentifyFaceResponse(BaseModel): + """人脸识别响应""" + + session_id: str + face_data: list[dict[str, Any]] + + +class FaceChooseItem(BaseModel): + """新版对口型人脸配置""" + + face_id: str = Field(..., description="人脸ID,由 identify-face 接口返回") + audio_id: str | None = Field(None, description="通过TTS生成的音频ID(与 sound_file 二选一)") + sound_file: str | None = Field(None, description="音频文件URL或Base64(与 audio_id 二选一)") + sound_start_time: int = Field(0, description="音频裁剪起点时间(ms)") + sound_end_time: int = Field(..., description="音频裁剪终点时间(ms)") + sound_insert_time: int = Field(0, description="裁剪后音频插入时间(ms)") + + +class AdvancedLipSyncRequest(BaseModel): + """新版对口型请求(advanced-lip-sync)""" + + session_id: str = Field(..., description="人脸识别返回的会话ID") + face_choose: list[FaceChooseItem] = Field(..., description="人脸对口型配置列表") + callback_url: str | None = Field(None, description="回调通知地址") + + +class OmniImageRequest(BaseModel): + """Omni-Image 图像生成请求""" + + prompt: str = Field(..., description="图像描述提示词") + model: str | None = Field("kling-image-o1", description="模型: kling-image-o1, kling-v3-omni") + aspect_ratio: str | None = Field("9:16", description="宽高比: 16:9/9:16/1:1/4:3/3:4") + resolution: str | None = Field("1k", description="清晰度: 1k/2k/4k") + result_type: str | None = Field("single", description="结果类型: single/series") + n: int | None = Field(1, description="生成数量 1-9") + element_list: list[dict[str, Any]] | None = Field(None, description="主体参考列表") + image_list: list[dict[str, Any]] | None = Field(None, description="参考图列表") + callback_url: str | None = Field(None, description="回调通知地址") + + +class ImageGenerateRequest(BaseModel): + """图像生成请求""" + + prompt: str = Field(..., description="图像描述提示词") + model: str | None = Field("kolors-v1", description="图像模型: kolors-v1") + width: int = Field(1024, description="图像宽度") + height: int = Field(1024, description="图像高度") + negative_prompt: str | None = Field(None, description="负面提示词") + callback_url: str | None = Field(None, description="回调通知地址") + + +class TaskStatusResponse(BaseModel): + """任务状态响应""" + + task_id: str + task_status: str # submitted, processing, succeed, failed + created_at: int + updated_at: int + video_url: str | None = None + image_url: str | None = None + error_message: str | None = None + + +class VirtualTryonRequest(BaseModel): + """虚拟试穿请求""" + + person_image_url: str = Field(..., description="人物图片 URL") + cloth_image_url: str = Field(..., description="衣服图片 URL") + callback_url: str | None = Field(None, description="回调通知地址") + + +# ============ 自定义音色 Schema ============ + + +class CreateCustomVoiceRequest(BaseModel): + """创建自定义音色请求""" + + voice_name: str = Field(..., description="音色名称(最多20字符)", example="我的音色") + audio_url: str | None = Field(None, description="音频文件URL(mp3/wav/mp4/mov)") + video_url: str | None = Field(None, description="视频文件URL") + video_id: str | None = Field( + None, description="历史作品ID(v2.6/sound=on/数字人/对口型生成的视频)" + ) + callback_url: str | None = Field(None, description="回调通知地址") + external_task_id: str | None = Field(None, description="自定义任务ID") + + +class ElementImage(BaseModel): + """主体参考图片(对应 KlingAI 官方格式 imageUrl)""" + + image_url: str = Field(..., description="图片URL") + name: str | None = Field(None, description="图片名称") + + +class ElementVideo(BaseModel): + """主体参考视频(对应 KlingAI 官方格式 videoUrl)""" + + video_url: str = Field(..., description="视频URL") + name: str | None = Field(None, description="视频名称") + + +class CreateElementRequest(BaseModel): + """创建主体请求""" + + element_name: str = Field(..., description="主体名称(最多20字符)", example="我的小猫") + element_description: str = Field( + ..., description="主体描述(最多100字符)", example="一只橘色的小猫,毛茸茸的" + ) + reference_type: str = Field("image_refer", description="参考类型: image_refer 或 video_refer") + element_image_list: list[ElementImage] | None = Field( + None, description="图片参考列表(图片定制时必填,第一个作为正面图)" + ) + element_video_list: list[ElementVideo] | None = Field( + None, description="视频参考列表(视频定制时必填,第一个作为正面视频)" + ) + element_voice_id: str | None = Field(None, description="音色ID,绑定音色到主体") + callback_url: str | None = Field(None, description="回调通知地址") + + +class ElementResponse(BaseModel): + """主体响应""" + + element_id: int | None = None + element_name: str | None = None + element_description: str | None = None + element_type: str | None = None # image_refer / video_refer + status: str | None = None + task_id: str | None = None + task_status: str | None = None + created_at: int | None = None + updated_at: int | None = None + element_image_list: dict | None = None + element_video_list: dict | None = None + element_voice_info: dict | None = None + owned_by: str | None = None + + +class CreateCustomVoiceResponse(BaseModel): + """创建自定义音色响应""" + + task_id: str + task_status: str + created_at: int + updated_at: int + + +class VoiceInfo(BaseModel): + """音色信息""" + + voice_id: str + voice_name: str + trial_url: str | None = None + owned_by: str | None = None + status: str | None = None + + +# ============ 辅助函数 ============ + + +async def get_klingai_provider() -> KlingAIProvider: + """获取 KlingAI Provider 实例 + + API Key 从 Settings 读取(符合配置规范) + """ + settings = get_settings() + config_loader = get_config_loader() + platform = config_loader.get_platform("klingai") + + if not platform: + raise HTTPException(status_code=404, detail="KlingAI 平台未配置") + + # 从 Settings 读取 AK/SK(符合配置规范:.env → Settings → 服务层) + access_key = settings.KLINGAI_ACCESS_KEY + secret_key = settings.KLINGAI_SECRET_KEY + + if not access_key or not secret_key: + raise HTTPException( + status_code=400, + detail="KlingAI Access Key 或 Secret Key 未配置,请设置环境变量 KLINGAI_ACCESS_KEY 和 KLINGAI_SECRET_KEY", + ) + + # 从 YAML 读取 base_url(模型配置) + base_url = platform.base_url if platform else None + + return KlingAIProvider( + { + "access_key": access_key, + "secret_key": secret_key, + "base_url": base_url or "https://api-beijing.klingai.com", + } + ) + + +# ============ API 路由 ============ + + +@router.post("/videos/omni", response_model=ApiResponse[VideoGenerateResponse]) +async def create_omni_video(data: OmniVideoRequest): + """ + Omni-Video 多模态视频生成 + + 支持文本、图片、主体、视频等多种输入方式组合生成视频。 + 适用于 kling-v3-omni 和 kling-video-o1 模型。 + + **特性:** + - 支持 3-15 秒视频生成 + - 支持多镜头和智能分镜 (shotType=intelligence) + - 支持引用主体、图片、视频作为参考 + - 支持 <<>>/<<>>/<<>> 语法引用提示词中的资源 + """ + try: + provider = await get_klingai_provider() + + result = await provider.generate_video_omni( + prompt=data.prompt, + model=data.model, + mode=data.mode, + aspect_ratio=data.aspect_ratio, + duration=data.duration, + sound=data.sound, + negative_prompt=data.negative_prompt, + multi_shot=data.multi_shot, + shot_type=data.shot_type, + multi_prompt=data.multi_prompt, + image_list=data.image_list, + element_list=data.element_list, + video_list=data.video_list, + callback_url=data.callback_url, + external_task_id=data.external_task_id, + ) + + return success_response(data=VideoGenerateResponse(**result)) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Omni-Video 生成失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/videos/omni/{task_id}", response_model=ApiResponse[TaskStatusResponse]) +async def get_omni_video_task(task_id: str): + """ + 查询 Omni-Video 任务状态 + + 查询指定 Omni-Video 任务的执行状态和结果。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.get_omni_video_task(task_id) + + return success_response(data=TaskStatusResponse(**result)) + + except HTTPException: + raise + except Exception as e: + logger.error(f"查询 Omni-Video 任务失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/videos/omni", response_model=ApiResponse[dict]) +async def list_omni_video_tasks( + page: int = 1, + page_size: int = 30, +): + """ + 查询 Omni-Video 任务列表 + + 查询历史 Omni-Video 任务列表。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.list_omni_video_tasks( + page=page, + page_size=page_size, + ) + + return success_response(data=result) + + except HTTPException: + raise + except Exception as e: + logger.error(f"查询 Omni-Video 任务列表失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/videos/image2video", response_model=ApiResponse[VideoGenerateResponse]) +async def create_image_to_video(data: Image2VideoRequest): + """ + 图生视频 + + 根据输入图片生成视频。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.generate_video_from_image( + image_url=data.image_url, + prompt=data.prompt, + model=data.model, + duration=data.duration, + aspect_ratio=data.aspect_ratio, + mode=data.mode, + callback_url=data.callback_url, + ) + + return success_response(data=VideoGenerateResponse(**result)) + + except HTTPException: + raise + except Exception as e: + logger.error(f"图生视频失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/videos/extend", response_model=ApiResponse[VideoGenerateResponse]) +async def extend_video( + video_id: str, + prompt: str | None = None, + duration: int = 5, + callback_url: str | None = None, +): + """ + 视频延长 + + 延长现有视频的时长。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.extend_video( + video_id=video_id, + prompt=prompt, + duration=duration, + callback_url=callback_url, + ) + + return success_response(data=VideoGenerateResponse(**result)) + + except HTTPException: + raise + except Exception as e: + logger.error(f"视频延长失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/videos/identify-face", response_model=ApiResponse[IdentifyFaceResponse]) +async def identify_face(data: IdentifyFaceRequest): + """ + 对口型前置:人脸识别 + + 分析视频中的人脸信息,返回 sessionId 和 faceId,用于后续的 advanced-lip-sync。 + """ + try: + if not data.videoId and not data.videoUrl: + raise HTTPException(status_code=400, detail="必须提供 videoId 或 videoUrl") + + provider = await get_klingai_provider() + result = await provider.identify_face(video_id=data.videoId, video_url=data.videoUrl) + + return success_response(data=IdentifyFaceResponse(**result)) + + except HTTPException: + raise + except Exception as e: + logger.error(f"人脸识别失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/videos/advanced-lip-sync", response_model=ApiResponse[VideoGenerateResponse]) +async def create_advanced_lip_sync(data: AdvancedLipSyncRequest): + """ + 新版对口型视频生成 + + 基于 KlingAI advanced-lip-sync 接口,先调用 /videos/identify-face 获取 sessionId 和 faceId, + 再传入本接口生成对口型视频。 + + 支持 audio_id(TTS 生成)或 soundFile(外部音频 URL)驱动口型。 + """ + try: + provider = await get_klingai_provider() + + face_choose = [item.model_dump() for item in data.faceChoose] + + result = await provider.advanced_lip_sync( + session_id=data.sessionId, + face_choose=face_choose, + callback_url=data.callbackUrl, + ) + + return success_response(data=VideoGenerateResponse(**result)) + + except HTTPException: + raise + except Exception as e: + logger.error(f"对口型生成失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/videos/advanced-lip-sync/{taskId}", response_model=ApiResponse[TaskStatusResponse]) +async def get_advanced_lip_sync_task(taskId: str): + """ + 查询新版对口型任务状态 + """ + try: + provider = await get_klingai_provider() + result = await provider.get_advanced_lip_sync_task(taskId) + + taskStatus = result.get("taskStatus", "unknown") + videos = result.get("task_result", {}).get("videos", []) + videoUrl = videos[0].get("url") if videos else None + + return success_response( + data=TaskStatusResponse( + taskId=result.get("taskId", taskId), + taskStatus=taskStatus, + createdAt=result.get("createdAt", 0), + updatedAt=result.get("updatedAt", 0), + videoUrl=videoUrl, + errorMessage=result.get("taskStatus_msg"), + ) + ) + + except HTTPException: + raise + except Exception as e: + logger.error(f"查询对口型任务失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/images/omni", response_model=ApiResponse[VideoGenerateResponse]) +async def create_omni_image(data: OmniImageRequest): + """ + Omni-Image 图像生成 + + 支持文本、主体、参考图等多种输入方式组合生成图像。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.generate_omni_image( + prompt=data.prompt, + model=data.model, + aspect_ratio=data.aspect_ratio, + resolution=data.resolution, + result_type=data.result_type, + n=data.n, + element_list=data.element_list, + image_list=data.image_list, + callback_url=data.callback_url, + ) + + return success_response(data=VideoGenerateResponse(**result)) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Omni-Image 生成失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/images/omni/{task_id}", response_model=ApiResponse[TaskStatusResponse]) +async def get_omni_image_task(task_id: str): + """ + 查询 Omni-Image 任务状态 + """ + try: + provider = await get_klingai_provider() + result = await provider.get_omni_image_task(task_id) + + task_status = result.get("task_status", "unknown") + images = result.get("task_result", {}).get("images", []) + image_url = images[0].get("url") if images else None + + return success_response( + data=TaskStatusResponse( + task_id=result.get("task_id", task_id), + task_status=task_status, + created_at=result.get("created_at", 0), + updated_at=result.get("updated_at", 0), + image_url=image_url, + error_message=result.get("task_status_msg"), + ) + ) + + except HTTPException: + raise + except Exception as e: + logger.error(f"查询 Omni-Image 任务失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/images/generations", response_model=ApiResponse[VideoGenerateResponse]) +async def create_image(data: ImageGenerateRequest): + """ + 文生图 + + 根据文本描述生成图像。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.generate_image( + prompt=data.prompt, + model=data.model, + width=data.width, + height=data.height, + negative_prompt=data.negativePrompt, + callback_url=data.callbackUrl, + ) + + return success_response(data=VideoGenerateResponse(**result)) + + except HTTPException: + raise + except Exception as e: + logger.error(f"图像生成失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/virtual-tryon", response_model=ApiResponse[VideoGenerateResponse]) +async def create_virtual_tryon(data: VirtualTryonRequest): + """ + 虚拟试穿 + + 将衣服虚拟试穿到人物身上。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.virtual_tryon( + person_image_url=data.person_imageUrl, + cloth_image_url=data.cloth_imageUrl, + callback_url=data.callbackUrl, + ) + + return success_response(data=VideoGenerateResponse(**result)) + + except HTTPException: + raise + except Exception as e: + logger.error(f"虚拟试穿失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/tasks/{taskId}", response_model=ApiResponse[TaskStatusResponse]) +async def get_taskStatus( + taskId: str, + task_type: str = "video", +): + """ + 查询任务状态 + + 查询指定任务的执行状态和结果。 + + Args: + taskId: 任务 ID + task_type: 任务类型 (video, image2video, image, lip-sync, virtual-tryon) + """ + try: + provider = await get_klingai_provider() + + result = await provider.get_taskStatus( + task_id=taskId, + task_type=task_type, + ) + + return success_response(data=TaskStatusResponse(**result)) + + except HTTPException: + raise + except Exception as e: + logger.error(f"查询任务状态失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/tasks", response_model=ApiResponse[dict]) +async def list_tasks( + task_type: str = "video", + page: int = 1, + page_size: int = 10, +): + """ + 查询任务列表 + + 查询历史任务列表。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.list_tasks( + task_type=task_type, + page=page, + page_size=page_size, + ) + + return success_response(data=result) + + except HTTPException: + raise + except Exception as e: + logger.error(f"查询任务列表失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +# ============ 主体管理 API ============ + + +@router.post("/elements", response_model=ApiResponse[ElementResponse]) +async def create_element(data: CreateElementRequest): + """ + 创建主体(自定义元素) + + 通过上传图片或视频创建可复用的主体,用于视频/图像生成时保持角色一致性。 + + 图片要求: + - 格式:jpg, jpeg, png + - 大小:≤10MB + - 数量:正面图 + 1-3张其他角度 + + 视频要求: + - 格式:mp4, mov + - 时长:3-8秒 + - 分辨率:1080P + - 大小:≤200MB + """ + try: + provider = await get_klingai_provider() + + imageList = None + if data.element_imageList: + imageList = { + "frontalImage": data.element_imageList[0].imageUrl, + "referImages": [ + {"imageUrl": img.imageUrl, "name": img.name} + for img in data.element_imageList[1:] + if img.imageUrl + ], + } + + videoList = None + if data.element_videoList: + videoList = { + "frontal_video": data.element_videoList[0].videoUrl, + "referVideos": [ + {"videoUrl": vid.videoUrl, "name": vid.name} + for vid in data.element_videoList[1:] + if vid.videoUrl + ], + } + + result = await provider.create_element( + element_name=data.elementName, + element_description=data.elementDescription, + reference_type=data.referenceType, + element_image_list=imageList, + element_video_list=videoList, + element_voice_id=data.element_voiceId, + callback_url=data.callbackUrl, + ) + + return success_response(data=ElementResponse(**result)) + + except HTTPException: + raise + except Exception as e: + logger.error(f"创建主体失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/elements", response_model=ApiResponse[list[ElementResponse]]) +async def list_elements(): + """ + 查询主体列表 + + 获取所有已创建的主体(自定义元素)列表。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.list_elements() + + elements = [ElementResponse(**item) for item in result if isinstance(item, dict)] + + return success_response(data=elements) + + except HTTPException: + raise + except Exception as e: + logger.error(f"查询主体列表失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/elements/{elementId}", response_model=ApiResponse[ElementResponse]) +async def get_element(elementId: str): + """ + 查询单个主体详情 + + 获取指定主体的详细信息。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.get_element(elementId) + + return success_response(data=ElementResponse(**result)) + + except HTTPException: + raise + except Exception as e: + logger.error(f"查询主体详情失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.delete("/elements/{elementId}", response_model=ApiResponse[dict]) +async def delete_element(elementId: str): + """ + 删除主体 + + 删除不再使用的主体(自定义元素)。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.delete_element(elementId) + + return success_response(data=result) + + except HTTPException: + raise + except Exception as e: + logger.error(f"删除主体失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +# ============ 智能补全主体图 API ============ + + +class AiMultiShotRequest(BaseModel): + """智能补全主体图请求""" + + frontal_image: str = Field( + ..., description="主体正面参考图 URL", example="https://example.com/front.jpg" + ) + callback_url: str | None = Field(None, description="回调通知地址") + + +class AiMultiShotResponse(BaseModel): + """智能补全主体图响应""" + + task_id: str + task_status: str + created_at: int + updated_at: int + + +@router.post("/elements/ai-multi-shot", response_model=ApiResponse[AiMultiShotResponse]) +async def ai_multiShot(data: AiMultiShotRequest): + """ + 智能补全主体不同角度图片 + + 通过主体正面图,自动推理出该主体其他角度图片。 + 每次可生成3组结果供选择,每次扣减0.5积分。 + + 使用流程: + 1. 调用此接口传入正面图 + 2. 轮询查询任务状态 + 3. 获取生成的多组角度图片 + 4. 选择合适的图片创建主体 + """ + try: + provider = await get_klingai_provider() + + result = await provider.ai_multiShot( + frontal_image=data.frontalImage, + callback_url=data.callbackUrl, + ) + + return success_response(data=AiMultiShotResponse(**result)) + + except HTTPException: + raise + except Exception as e: + logger.error(f"智能补全主体图失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/elements/ai-multi-shot/{taskId}", response_model=ApiResponse[dict]) +async def get_ai_multiShot_task(taskId: str): + """ + 查询智能补全主体图任务状态 + + 获取指定任务的执行状态和生成的多角度图片结果。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.get_ai_multiShot_task(taskId) + + return success_response(data=result) + + except HTTPException: + raise + except Exception as e: + logger.error(f"查询智能补全任务失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +# ============ 自定义音色 API ============ + + +@router.post("/voices/custom", response_model=ApiResponse[CreateCustomVoiceResponse]) +async def create_custom_voice(data: CreateCustomVoiceRequest): + """ + 创建自定义音色 + + 通过上传音频文件或引用历史视频创建自定义音色,用于对口型视频。 + + 音频要求: + - 格式:mp3, wav, mp4, mov + - 时长:5-30 秒 + - 人声干净、无杂音、单一人声 + """ + try: + provider = await get_klingai_provider() + + result = await provider.create_custom_voice( + voice_name=data.voiceName, + audio_url=data.audioUrl, + video_url=data.videoUrl, + video_id=data.videoId, + callback_url=data.callbackUrl, + external_task_id=data.externalTaskId, + ) + + return success_response(data=CreateCustomVoiceResponse(**result)) + + except HTTPException: + raise + except Exception as e: + logger.error(f"创建自定义音色失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/voices/custom", response_model=ApiResponse[list[VoiceInfo]]) +async def list_custom_voices(): + """ + 查询自定义音色列表 + + 获取所有已创建的自定义音色。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.list_custom_voices() + + voices = [] + for item in result: + if isinstance(item, dict) and "task_result" in item: + task_result = item.get("task_result", {}) + voices_data = task_result.get("voices", []) + for voice in voices_data: + voices.append(VoiceInfo(**voice)) + + return success_response(data=voices) + + except HTTPException: + raise + except Exception as e: + logger.error(f"查询自定义音色列表失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/voices/custom/{voiceId}", response_model=ApiResponse[dict]) +async def get_custom_voice(voiceId: str): + """ + 查询单个自定义音色 + + 获取指定自定义音色的详细信息。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.get_custom_voice(voiceId) + + return success_response(data=result) + + except HTTPException: + raise + except Exception as e: + logger.error(f"查询自定义音色失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/voices/presets", response_model=ApiResponse[list[VoiceInfo]]) +async def list_preset_voices(): + """ + 查询官方预设音色列表 + + 获取 KlingAI 提供的官方音色列表。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.list_preset_voices() + + voices = [] + for item in result: + if isinstance(item, dict) and "task_result" in item: + task_result = item.get("task_result", {}) + voices_data = task_result.get("voices", []) + for voice in voices_data: + voices.append(VoiceInfo(**voice)) + + return success_response(data=voices) + + except HTTPException: + raise + except Exception as e: + logger.error(f"查询官方音色列表失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.delete("/voices/custom/{voiceId}", response_model=ApiResponse[dict]) +async def delete_custom_voice(voiceId: str): + """ + 删除自定义音色 + + 删除不再使用的自定义音色。 + """ + try: + provider = await get_klingai_provider() + + result = await provider.delete_custom_voice(voiceId) + + return success_response(data=result) + + except HTTPException: + raise + except Exception as e: + logger.error(f"删除自定义音色失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) diff --git a/python-api/app/api/v1/qiniu.py b/python-api/app/api/v1/qiniu.py new file mode 100644 index 0000000..013cad7 --- /dev/null +++ b/python-api/app/api/v1/qiniu.py @@ -0,0 +1,339 @@ +""" +七牛云对象存储 API 路由 +======================== + +提供音视频文件上传、管理和访问功能。 + +主要功能: +1. 生成上传凭证(客户端直传) +2. 服务端文件上传 +3. 声音克隆样本上传 +4. 文件删除和管理 +""" + +import contextlib +import logging +import os +import shutil +import tempfile +from pathlib import Path + +logger = logging.getLogger(__name__) + +from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile +from pydantic import BaseModel, Field + +from app.api.deps import get_current_user +from app.models.user import User +from app.schemas.common import ApiResponse, success_response +from app.services.qiniu_service import get_qiniu_service + +router = APIRouter(prefix="/qiniu", tags=["Qiniu Storage"]) + + +# ============ 请求/响应模型 ============ + + +class UploadTokenRequest(BaseModel): + """上传凭证请求""" + + key: str = Field(..., description="文件存储 Key") + expires: int = Field(3600, description="Token 有效期(秒)") + + +class UploadTokenResponse(BaseModel): + """上传凭证响应""" + + token: str + key: str + uploadUrl: str = "https://upload.qiniup.com" + + +class FileUploadResponse(BaseModel): + """文件上传响应""" + + key: str + url: str + hash: str + mimeType: str + fsize: int + isDuplicate: bool = False + message: str | None = None + existingTaskId: str | None = None # 当检测到重复任务时返回 + + +class DeleteFileRequest(BaseModel): + """删除文件请求""" + + key: str = Field(..., description="文件 Key") + + +# ============ API 路由 ============ + + +@router.post("/upload-token", response_model=ApiResponse[UploadTokenResponse]) +async def get_upload_token(request: UploadTokenRequest): + """ + 获取上传凭证(客户端直传) + + 前端获取 Token 后,可直接上传到七牛云,无需经过服务端。 + + 上传地址: https://upload.qiniup.com + 请求方式: POST (multipart/form-data) + 请求参数: + - token: 上传凭证(本接口返回) + - key: 文件存储 Key(本接口返回) + - file: 文件内容 + """ + try: + service = get_qiniu_service() + token = service.get_upload_token(request.key, request.expires) + + return success_response( + data=UploadTokenResponse( + token=token, key=request.key, uploadUrl="https://upload.qiniup.com" + ) + ) + + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail=f"生成上传凭证失败: {e}") + + +@router.post("/upload/audio", response_model=ApiResponse[FileUploadResponse]) +async def upload_audio( + file: UploadFile = File(..., description="音频文件(MP3, WAV, M4A, AAC, OGG)"), + userId: str | None = Form(None, description="用户ID(可选,用于目录隔离)"), +): + """ + 上传音频文件 + + 支持格式: MP3, WAV, M4A, AAC, OGG + 文件会自动存储到: audios/{userId}/{date}/{uuid}.{ext} + """ + service = get_qiniu_service() + + # 保存临时文件 + suffix = Path(file.filename).suffix if file.filename else ".mp3" + with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp: + shutil.copyfileobj(file.file, tmp) + tmp_path = tmp.name + + try: + result = service.upload_audio(tmp_path, userId=userId) + return success_response(data=FileUploadResponse(**result)) + + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail=f"上传失败: {e}") + finally: + os.unlink(tmp_path) + + +@router.post("/upload/video", response_model=ApiResponse[FileUploadResponse]) +async def upload_video( + file: UploadFile = File(..., description="视频文件(MP4, MOV, AVI, WebM)"), + userId: str | None = Form(None, description="用户ID(可选,用于目录隔离)"), +): + """ + 上传视频文件 + + 支持格式: MP4, MOV, AVI, WebM + 文件会自动存储到: videos/{userId}/{date}/{uuid}.{ext} + """ + service = get_qiniu_service() + + suffix = Path(file.filename).suffix if file.filename else ".mp4" + with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp: + shutil.copyfileobj(file.file, tmp) + tmp_path = tmp.name + + try: + result = service.upload_video(tmp_path, userId=userId) + return success_response(data=FileUploadResponse(**result)) + + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + raise HTTPException(status_code=500, detail=f"上传失败: {e}") + finally: + os.unlink(tmp_path) + + +async def _check_existing_avatar_task( + video_url: str, + user_id: str, +) -> dict | None: + """ + 检查是否有相同视频URL的正在进行的任务(从 Redis 读取) + + Returns: + 如果找到进行中的任务,返回 {'task_id': str, 'status': str} + 否则返回 None + """ + import json + + from app.core.redis_client import get_redis_client + from app.scheduler.registry import JobRegistry + + redis = get_redis_client() + registry = JobRegistry(redis) + job_ids = await registry.get_running_job_ids() + + for job_id in job_ids: + data = await redis.hgetall(f"job:{job_id}") + if not data: + continue + if data.get("type") != "avatar_clone": + continue + + params = {} + if "params" in data and data["params"]: + with contextlib.suppress(json.JSONDecodeError): + params = json.loads(data["params"]) + + if params.get("user_id") == user_id and params.get("video_url") == video_url: + avatar_status = data.get("avatar_status", data.get("status", "")) + return { + "task_id": job_id, + "status": avatar_status, + "voice_id": data.get("voice_id"), + "provider_element_id": data.get("provider_element_id"), + "video_url": video_url, + "file_size": 0, + } + return None + + +@router.post("/upload/avatar", response_model=ApiResponse[FileUploadResponse]) +async def upload_avatar( + file: UploadFile = File(..., description="形象克隆视频(MP4, MOV)"), + userId: str | None = Form(None, description="用户ID(可选,用于目录隔离)"), + fileHash: str | None = Form(None, description="前端计算的文件SHA256哈希,用于重复检测"), + current_user: User = Depends(get_current_user), +): + """ + 上传形象克隆视频 + + 用于形象克隆功能,上传的视频将同时用于创建自定义音色和主体。 + + KlingAI 要求: + - 格式: MP4, MOV + - 时长: 5-30 秒 (建议 5-8 秒) + - 大小: 不超过 200MB + - 分辨率: 高度 720px~2160px + - 内容: 写实风格人物正面特写,人脸清晰、无遮挡,视频中有清晰人声 + + 文件存储路径: meijiaka/avatars/{userId}/{date}/{uuid}.{ext} + + 重复检测: + - 如果提供了 fileHash,会检查是否已有相同文件的任务在进行中 + - 返回的 isDuplicate 表示是否复用了已有资源 + - existingTaskId 表示已存在任务的ID(如果有) + """ + service = get_qiniu_service() + + # 使用当前登录用户的ID + effective_user_id = userId or str(current_user.id) + + suffix = Path(file.filename).suffix if file.filename else ".mp4" + with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp: + shutil.copyfileobj(file.file, tmp) + tmp_path = tmp.name + + try: + result = service.upload_avatar_video( + tmp_path, + user_id=effective_user_id, + file_hash=fileHash, + ) + + # 如果七牛云返回了现有文件,检查数据库中是否有进行中的任务 + if result.get("isDuplicate") and result.get("url"): + existing_task = await _check_existing_avatar_task(result["url"], effective_user_id) + if existing_task: + logger.info( + f"Found existing avatar task for uploaded file: {existing_task['task_id']}" + ) + result["existingTaskId"] = existing_task["task_id"] + result["message"] = "检测到相同视频的任务正在进行中,已复用现有任务" + + return success_response(data=FileUploadResponse(**result)) + + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + logger.exception("Upload avatar failed") + raise HTTPException(status_code=500, detail=f"上传失败: {e}") + finally: + os.unlink(tmp_path) + + +@router.get("/files/{key:path}", response_model=ApiResponse[dict]) +async def get_file_info(key: str): + """ + 获取文件信息 + + Args: + key: 文件存储 Key(路径格式) + """ + try: + service = get_qiniu_service() + # 根据 key 推断 bucket + bucket = service.image_bucket if "/images/" in key else service.video_bucket + info = service.get_file_info(bucket, key) + + if info is None: + raise HTTPException(status_code=404, detail="文件不存在") + + return success_response(data=info) + + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500, detail=f"获取文件信息失败: {e}") + + +@router.delete("/files/{key:path}", response_model=ApiResponse[dict]) +async def delete_file(key: str): + """ + 删除文件 + + Args: + key: 文件存储 Key + """ + try: + service = get_qiniu_service() + # 根据 key 推断 bucket + bucket = service.image_bucket if "/images/" in key else service.video_bucket + success = service.delete_file(bucket, key) + + return success_response( + data={ + "success": success, + "key": key, + "message": "删除成功" if success else "删除失败或文件不存在", + } + ) + + except Exception as e: + raise HTTPException(status_code=500, detail=f"删除失败: {e}") + + +@router.post("/refresh-cdn", response_model=ApiResponse[dict]) +async def refresh_cdn(keys: list[str]): + """ + 刷新 CDN 缓存 + + 文件更新后,调用此接口刷新 CDN 缓存,确保用户访问到最新内容。 + """ + try: + service = get_qiniu_service() + result = service.refresh_cdn(keys) + + return success_response(data=result) + + except Exception as e: + raise HTTPException(status_code=500, detail=f"刷新 CDN 失败: {e}") diff --git a/python-api/app/api/v1/router.py b/python-api/app/api/v1/router.py new file mode 100644 index 0000000..f94cdd2 --- /dev/null +++ b/python-api/app/api/v1/router.py @@ -0,0 +1,51 @@ +""" +API v1 路由聚合 +============== +""" + +from fastapi import APIRouter + +from app.api.v1 import ( + ai_models, + auth, + avatar, + caption, + klingai, + qiniu, + script, + system, + tasks, + video, +) + +api_router = APIRouter() + +# 认证模块 +api_router.include_router(auth.router, prefix="/auth", tags=["Authentication"]) + +# 脚本模块 +api_router.include_router(script.router, prefix="/script", tags=["Script"]) + +# AI 平台管理模块 +api_router.include_router(ai_models.router, prefix="/ai", tags=["AI Models"]) + +# KlingAI 模块(视频/图像生成) +api_router.include_router(klingai.router, tags=["KlingAI"]) + +# 七牛云对象存储模块 +api_router.include_router(qiniu.router, tags=["Qiniu Storage"]) + +# 视频生成模块 +api_router.include_router(video.router, tags=["Video"]) + +# 形象克隆模块 +api_router.include_router(avatar.router, tags=["Avatar"]) + +# 系统模块 +api_router.include_router(system.router, prefix="/system", tags=["System"]) + +# 字幕生成模块(火山引擎-豆包语音) +api_router.include_router(caption.router, tags=["Caption"]) + +# 统一任务管理模块 +api_router.include_router(tasks.router, tags=["Tasks"]) diff --git a/python-api/app/api/v1/script.py b/python-api/app/api/v1/script.py new file mode 100644 index 0000000..2aada9c --- /dev/null +++ b/python-api/app/api/v1/script.py @@ -0,0 +1,187 @@ +""" +脚本生成 API +============ + +提供脚本生成、润色、模型健康检查等功能。 +支持 SSE 流式响应。 +""" + +from __future__ import annotations + +import logging + +from fastapi import APIRouter, Request +from fastapi.responses import StreamingResponse + +from app.schemas.common import ApiResponse, success_response +from app.schemas.script import ( + GenerateScriptRequest, + ModelHealthResponse, + PolishRequest, + ScriptGenerationEvent, + ScriptShot, + TestModelRequest, + TestModelResponse, +) +from app.services.script_service import get_script_service + +router = APIRouter() +logger = logging.getLogger(__name__) + + +@router.post("/generate", response_model=ApiResponse[list[ScriptShot]]) +async def generate_script(request: GenerateScriptRequest): + """ + 同步生成脚本 + + 直接返回生成的分镜列表,适合快速预览。 + """ + service = get_script_service() + + shots = await service.generate_script( + topic=request.topic, + duration=request.duration, + script_type=request.script_type, + model=request.model, + ) + + return success_response( + data=shots, + message=f"成功生成 {len(shots)} 个分镜", + ) + + +@router.post("/generate/stream") +async def generate_script_stream(request: Request, data: GenerateScriptRequest): + """ + 流式生成脚本(SSE) + + 返回 Server-Sent Events,包含进度更新和最终结果。 + 前端通过 EventSource 接收实时进度。 + + **SSE 事件类型:** + - `start`: 开始生成 + - `analyzing`: 分析主题 + - `planning`: 规划结构 + - `generating`: AI 生成中 + - `parsing`: 解析结果 + - `complete`: 完成,包含 result 字段 + - `error`: 错误 + + **示例事件流:** + ``` + data: {"type": "start", "progress": 0, "message": "开始生成脚本"} + + data: {"type": "analyzing", "progress": 15, "message": "分析目标受众..."} + + data: {"type": "complete", "progress": 100, "message": "成功生成 5 个分镜", "result": [...]} + ``` + """ + service = get_script_service() + + async def event_generator(): + """SSE 事件生成器,带客户端断开检测""" + try: + async for event in service.generate_script_stream( + topic=data.topic, + duration=data.duration, + script_type=data.script_type, + model=data.model, + ): + # 检查客户端是否已断开 + if await request.is_disconnected(): + logger.info("[SSE] 客户端已断开连接,停止生成") + break + + # SSE 格式:data: {...}\n\n + try: + yield f"data: {event.model_dump_json()}\n\n" + except Exception as e: + logger.error(f"[SSE] 序列化事件失败: {e}") + continue + + # 发送结束标记(如果客户端还连接着) + if not await request.is_disconnected(): + yield "data: [DONE]\n\n" + + except Exception as e: + logger.exception("[SSE] 事件生成器异常") + # 尝试发送错误信息给客户端 + try: + error_event = ScriptGenerationEvent( + type="error", + progress=0, + message=f"服务器错误: {str(e)}", + ) + yield f"data: {error_event.model_dump_json()}\n\n" + yield "data: [DONE]\n\n" + except: + pass + + return StreamingResponse( + event_generator(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "X-Accel-Buffering": "no", # 禁用 Nginx 缓冲 + }, + ) + + +@router.post("/polish", response_model=ApiResponse[str]) +async def polish_content(request: PolishRequest): + """ + AI 润色文案/画面描述 + + - `polishType=scene`: 润色画面描述(根据 shot_type 自动区分分镜/空镜) + - `polishType=voiceover`: 润色配音文案 + + 参数: + - `shot_type`: "segment"(分镜)或 "empty_shot"(空镜),画面润色时必填 + """ + service = get_script_service() + + polished = await service.polish_content( + content=request.content, + polish_type=request.polish_type, + shot_type=request.shot_type or "segment", + ) + + type_name = "画面" if request.polish_type == "scene" else "文案" + return success_response( + data=polished, + message=f"{type_name}润色完成", + ) + + +@router.get("/model-health", response_model=ApiResponse[ModelHealthResponse]) +async def check_model_health(): + """ + 检查 AI 模型健康状态 + + 返回所有配置的模型及其可用性状态。 + """ + service = get_script_service() + health_data = await service.check_model_health() + + return success_response( + data=ModelHealthResponse(**health_data), + message="模型健康检查完成", + ) + + +@router.post("/test-model", response_model=ApiResponse[TestModelResponse]) +async def test_model(request: TestModelRequest): + """ + 测试指定模型连接 + + 发送一个简单的测试请求,验证模型是否可用。 + """ + service = get_script_service() + result = await service.test_model(request.model_id) + + return success_response( + data=TestModelResponse(**result), + message="模型测试完成" if result["success"] else f"模型测试失败: {result.get('error')}", + ) diff --git a/python-api/app/api/v1/system.py b/python-api/app/api/v1/system.py new file mode 100644 index 0000000..254b140 --- /dev/null +++ b/python-api/app/api/v1/system.py @@ -0,0 +1,43 @@ +""" +系统模块 API +============ +""" + +from fastapi import APIRouter + +from app.schemas.common import ApiResponse, success_response + +router = APIRouter() + + +@router.get("/health", response_model=ApiResponse[dict]) +async def system_health(): + """系统健康检查(详细版)""" + return success_response( + data={ + "status": "healthy", + "services": { + "api": "up", + "database": "unknown", # TODO: 检查数据库连接 + "redis": "unknown", # TODO: 检查 Redis 连接 + }, + }, + message="系统运行正常", + ) + + +@router.get("/version", response_model=ApiResponse[dict]) +async def system_version(): + """获取系统版本信息""" + from app.config import get_settings + + settings = get_settings() + + return success_response( + data={ + "name": settings.APP_NAME, + "version": settings.APP_VERSION, + "environment": settings.ENV, + }, + message="获取版本成功", + ) diff --git a/python-api/app/api/v1/tasks.py b/python-api/app/api/v1/tasks.py new file mode 100644 index 0000000..31937d4 --- /dev/null +++ b/python-api/app/api/v1/tasks.py @@ -0,0 +1,499 @@ +""" +统一任务管理 API +=============== + +提供任务创建和状态查询接口,支持: +- video: 视频生成 +- image: 图片生成 +- script: 脚本生成 +- subtitle: 字幕对齐 +- copy: 文案提取 +- avatar_clone: 形象克隆 +""" + +import json +import logging +import uuid +from datetime import UTC, datetime +from typing import Literal + +from fastapi import APIRouter, Depends, HTTPException +from pydantic import BaseModel, Field, field_validator + +from app.api.deps import get_current_user +from app.core.redis_client import get_redis_client +from app.models.user import User +from app.scheduler.registry import JobRegistry +from app.schemas.enums import AvatarCloneStatus +from app.schemas.segment import Segment + +logger = logging.getLogger(__name__) + +router = APIRouter(prefix="/tasks", tags=["Tasks"]) + + +# ========== 请求/响应模型 ========== + + +class VideoParams(BaseModel): + """视频生成参数""" + + segments: list[Segment] = Field(..., description="分镜列表") + human_id: int | None = Field(None, description="数字人主体ID") + + @field_validator("segments") + @classmethod + def validate_segments(cls, v: list[Segment]) -> list[Segment]: + if not v: + raise ValueError("segments 不能为空列表") + return v + + +class ImageParams(BaseModel): + """图片生成参数""" + + prompt: str = Field(..., min_length=1, description="图片描述") + image_type: str = Field(default="cover", description="图片类型: empty_shot/cover") + reference_image: str | None = Field(None, description="参考图片URL(图生图)") + human_id: int | None = Field(None, description="数字人主体ID(omni-image使用)") + + @field_validator("prompt") + @classmethod + def validate_prompt(cls, v: str) -> str: + if not v or not v.strip(): + raise ValueError("prompt 不能为空") + return v.strip() + + +class ScriptParams(BaseModel): + """脚本生成参数""" + + topic: str = Field(..., min_length=1, description="创作主题") + style: str = Field(default="default", description="脚本风格") + duration: int = Field(default=60, ge=10, le=300, description="视频时长(秒)") + + @field_validator("topic") + @classmethod + def validate_topic(cls, v: str) -> str: + if not v or not v.strip(): + raise ValueError("topic 不能为空") + return v.strip() + + +class SubtitleParams(BaseModel): + """字幕生成参数""" + + video_path: str = Field(..., min_length=1, description="视频文件路径") + language: str = Field(default="zh", description="语言代码") + mode: str = Field(default="caption", description="模式: caption/auto_align") + audio_text: str | None = Field(default=None, description="打轴文本(auto_align 模式必填)") + + @field_validator("video_path") + @classmethod + def validate_video_path(cls, v: str) -> str: + if not v or not v.strip(): + raise ValueError("video_path 不能为空") + return v.strip() + + +class CopyParams(BaseModel): + """文案提取参数""" + + video_url: str = Field(..., min_length=1, description="视频链接URL") + + @field_validator("video_url") + @classmethod + def validate_video_url(cls, v: str) -> str: + if not v or not v.strip(): + raise ValueError("video_url 不能为空") + if not v.startswith(("http://", "https://")): + raise ValueError("video_url 必须是有效的URL") + return v.strip() + + +class TaskCreateRequest(BaseModel): + """创建任务请求""" + + project_id: str | None = Field(None, description="项目ID(可选)") + params: dict = Field(default_factory=dict, description="任务参数") + + +class TaskCreateResponse(BaseModel): + """创建任务响应""" + + task_id: str = Field(..., description="任务ID") + status: str = Field("pending", description="任务状态") + message: str = Field("任务已创建", description="状态消息") + + +class TaskStatusResponse(BaseModel): + """任务状态响应""" + + task_id: str = Field(..., description="任务ID") + type: str | None = Field(None, description="任务类型") + status: str = Field(..., description="任务状态: pending/running/waiting/completed/failed") + progress: int = Field(0, description="进度百分比 (0-100)") + message: str = Field("", description="状态描述") + completed: int = Field(0, description="已完成子任务数") + total: int = Field(0, description="总子任务数") + result: dict | None = Field(None, description="任务结果(完成时)") + error: str | None = Field(None, description="错误信息(失败时)") + created_at: str = Field("", description="任务创建时间(ISO格式)") + + +# ========== 辅助函数 ========== + + +def _generate_task_id() -> str: + """生成任务ID""" + return f"task_{uuid.uuid4().hex[:16]}" + + +# ========== API 路由 ========== + + +@router.post("/{task_type}", response_model=TaskCreateResponse) +async def create_task( + task_type: Literal["video", "image", "script", "subtitle", "copy", "avatar_clone"], + request: TaskCreateRequest, + current_user: User = Depends(get_current_user), +) -> TaskCreateResponse: + """ + 创建新任务 + + 根据任务类型写入 Redis,由 Async Engine Scheduler 统一调度。 + """ + task_id = _generate_task_id() + user_id = str(current_user.id) + project_id = request.project_id or request.params.get("project_id", "") + + redis = get_redis_client() + registry = JobRegistry(redis) + + try: + await registry.create(task_id, task_type, user_id) + except Exception as e: + logger.error(f"[API] Failed to create registry entry: {e}") + raise HTTPException(status_code=500, detail="创建任务失败:Redis连接错误") + + try: + if task_type == "video": + # 字段适配:前端 shots/element_id → 后端 segments/human_id + import re + + video_params = dict(request.params) + if "shots" in video_params: + shots = video_params.pop("shots") + for s in shots: + # 清洗 id:前端可能发送数字,Segment 模型要求 str + if "id" in s and not isinstance(s["id"], str): + s["id"] = str(s["id"]) + # 清洗 duration:前端可能发送 "5s",Segment 模型要求 int + duration = s.get("duration") + if isinstance(duration, str): + m = re.search(r"\d+", duration) + s["duration"] = int(m.group()) if m else None + video_params["segments"] = shots + if "element_id" in video_params: + video_params["human_id"] = video_params.pop("element_id") + validated = VideoParams(**video_params) + segments = validated.segments + human_id = validated.human_id + + normalized_segments = [] + for s in segments: + normalized_segments.append( + { + "id": str(s.id), + "type": s.type, + "scene": s.scene, + "voiceover": s.voiceover, + "duration": s.duration, + "human_id": (human_id if s.type == "segment" else None), + "voice_id": s.voice_id, + "provider_task_id": None, + "status": "pending", + "video_url": None, + "local_path": None, + "qiniu_url": None, + "error_message": None, + } + ) + + await registry.update( + task_id, + status="running", + message=f"开始生成视频,共 {len(normalized_segments)} 个镜头...", + completed=0, + total=len(normalized_segments), + params={ + "project_id": project_id, + "user_id": user_id, + "human_id": human_id, + "shots": json.dumps(normalized_segments, ensure_ascii=False), + }, + ) + await registry.add_running(task_id) + + elif task_type == "image": + image_validated = ImageParams(**request.params) + await registry.update( + task_id, + status="running", + message="准备生成图片...", + completed=0, + total=1, + params={ + "project_id": project_id, + "user_id": user_id, + "prompt": image_validated.prompt, + "image_type": image_validated.image_type, + "reference_image": image_validated.reference_image, + "human_id": image_validated.human_id, + }, + ) + await registry.add_running(task_id) + + elif task_type == "script": + script_validated = ScriptParams(**request.params) + await registry.update( + task_id, + status="running", + progress=0, + message="等待执行...", + params={ + "topic": script_validated.topic, + "style": script_validated.style, + "duration": script_validated.duration, + }, + ) + await registry.add_running(task_id) + + elif task_type == "subtitle": + subtitle_validated = SubtitleParams(**request.params) + await registry.update( + task_id, + status="running", + message="准备字幕生成...", + completed=0, + total=1, + params={ + "project_id": project_id, + "video_path": subtitle_validated.video_path, + "language": subtitle_validated.language, + "mode": subtitle_validated.mode, + "audio_text": subtitle_validated.audio_text, + }, + ) + await registry.add_running(task_id) + + elif task_type == "copy": + copy_validated = CopyParams(**request.params) + await registry.update( + task_id, + status="running", + message="准备提取文案...", + completed=0, + total=1, + params={"video_url": copy_validated.video_url}, + ) + await registry.add_running(task_id) + + elif task_type == "avatar_clone": + name = request.params.get("name", "").strip() + video_url = request.params.get("video_url", "").strip() + if not name: + raise ValueError("name 不能为空") + if not video_url: + raise ValueError("video_url 不能为空") + if not video_url.startswith(("http://", "https://")): + raise ValueError("video_url 必须是有效的URL") + + avatar_id = f"avt_{uuid.uuid4().hex[:16]}" + now = datetime.now(UTC).isoformat() + + # avatar_clone 使用自己的 task_id(avt_xxx),不走通用的 task_xxx + await registry.create(avatar_id, "avatar_clone", user_id) + await registry.update( + avatar_id, + status="running", + progress=5, + message="开始形象克隆...", + completed=0, + total=1, + params={ + "avatar_id": avatar_id, + "name": name, + "video_url": video_url, + "user_id": user_id, + }, + avatar_status=AvatarCloneStatus.PENDING.value, + avatar_name=name, + avatar_video_url=video_url, + voice_id="", + provider_element_id="", + provider_voice_job_id="", + provider_element_job_id="", + trial_url="", + fail_reason="", + created_at=now, + updated_at=now, + ) + await registry.add_running(avatar_id) + # 返回的任务 ID 用 avatar_id,保持前端兼容 + task_id = avatar_id + + else: + raise HTTPException(status_code=400, detail=f"不支持的任务类型: {task_type}") + + logger.info(f"[API] Task created: {task_id}, type={task_type}, user={user_id}") + return TaskCreateResponse( + task_id=task_id, + status="pending", + message=f"{task_type} 任务已创建", + ) + + except ValueError as e: + logger.warning(f"[API] Invalid params for {task_type}: {e}") + try: + await registry.update(task_id, status="failed", message=f"参数错误: {e}", error=str(e)) + except Exception as registry_err: + logger.warning(f"[API] Failed to update registry for {task_id}: {registry_err}") + raise HTTPException(status_code=422, detail=f"参数错误: {e}") + + except HTTPException: + raise + + except Exception as e: + logger.exception(f"[API] Failed to create task: {e}") + try: + await registry.update(task_id, status="failed", message=str(e), error=str(e)) + except Exception as registry_err: + logger.warning(f"[API] Failed to update registry for {task_id}: {registry_err}") + raise HTTPException(status_code=500, detail=f"创建任务失败: {str(e)}") + + +def _map_avatar_status(status: str) -> str: + """将 AvatarCloneStatus 映射为统一任务状态""" + mapping = { + "succeed": "completed", + "voice_failed": "failed", + "element_failed": "failed", + "timeout": "failed", + "pending": "running", + "voice_processing": "running", + "element_pending": "running", + "element_processing": "running", + } + return mapping.get(status, "running") + + +@router.get("", response_model=list[TaskStatusResponse]) +async def list_tasks( + project_id: str | None = None, + current_user: User = Depends(get_current_user), +) -> list[TaskStatusResponse]: + """ + 查询当前用户所有进行中的任务 + + 从 Redis running 集合读取真实状态,支持按 project_id 过滤。 + """ + redis = get_redis_client() + registry = JobRegistry(redis) + + try: + jobs = await registry.list_running_by_user(str(current_user.id)) + except Exception as e: + logger.error(f"[API] Redis error when listing tasks: {e}") + raise HTTPException(status_code=503, detail="服务暂时不可用,请稍后重试") + + results: list[TaskStatusResponse] = [] + for job in jobs: + # 按 project_id 过滤 + if project_id and job.project_id != project_id: + continue + results.append( + TaskStatusResponse( + task_id=job.job_id, + type=job.job_type, + status=job.status, + progress=job.progress, + message=job.message, + completed=job.completed, + total=job.total, + result=None, # 列表查询不返回 result,避免数据过大 + error=job.error, + created_at=job.created_at, + ) + ) + return results + + +@router.get("/{task_id}", response_model=TaskStatusResponse) +async def get_task_status( + task_id: str, + current_user: User = Depends(get_current_user), +) -> TaskStatusResponse: + """ + 查询任务状态 + + 前端通过轮询此接口获取任务进度。 + 任务状态仅从 Redis 查询,记录过期后返回 404。 + """ + redis = get_redis_client() + registry = JobRegistry(redis) + + try: + job = await registry.get(task_id) + except Exception as e: + logger.error(f"[API] Redis error when getting task {task_id}: {e}") + raise HTTPException(status_code=503, detail="服务暂时不可用,请稍后重试") + + if not job: + raise HTTPException(status_code=404, detail="任务不存在或已过期") + + # 权限检查 + if job.user_id != str(current_user.id): + raise HTTPException(status_code=403, detail="无权访问此任务") + + return TaskStatusResponse( + task_id=task_id, + type=job.job_type, + status=job.status, + progress=job.progress, + message=job.message, + completed=job.completed, + total=job.total, + result=job.result, + error=job.error, + created_at=job.created_at, + ) + + +@router.get("/{task_id}/result") +async def get_task_result( + task_id: str, + current_user: User = Depends(get_current_user), +) -> dict: + """ + 获取任务结果(简化接口,直接返回 result 字段) + """ + redis = get_redis_client() + registry = JobRegistry(redis) + + try: + job = await registry.get(task_id) + except Exception as e: + logger.error(f"[API] Redis error when getting result {task_id}: {e}") + raise HTTPException(status_code=503, detail="服务暂时不可用,请稍后重试") + + if not job: + raise HTTPException(status_code=404, detail="任务不存在或已过期") + + if job.user_id != str(current_user.id): + raise HTTPException(status_code=403, detail="无权访问此任务") + + if job.status != "completed": + raise HTTPException(status_code=400, detail=f"任务未完成,当前状态: {job.status}") + + return job.result or {} diff --git a/python-api/app/api/v1/video.py b/python-api/app/api/v1/video.py new file mode 100644 index 0000000..c6c60a4 --- /dev/null +++ b/python-api/app/api/v1/video.py @@ -0,0 +1,511 @@ +""" +视频生成 API 路由 +================ + +提供数字人视频、文生视频、图生视频功能。 +基于 KlingAI API 实现。 +""" + +import logging +import uuid +from datetime import datetime +from pathlib import Path + +from fastapi import APIRouter, File, Form, HTTPException, UploadFile +from fastapi.responses import FileResponse, StreamingResponse +from pydantic import BaseModel, Field + +from app.ai.providers.klingai_provider import KlingAIProvider +from app.config import get_settings +from app.core.config_loader import get_config_loader +from app.schemas.common import ApiResponse, success_response +from app.schemas.segment import Segment +from app.services.kling_video_service import get_kling_video_service + +router = APIRouter(prefix="/video", tags=["Video"]) + +# 视频文件存储目录 +VIDEO_STORAGE_DIR = Path("data/video") +VIDEO_STORAGE_DIR.mkdir(parents=True, exist_ok=True) + +# 上传文件临时目录 +UPLOAD_DIR = Path("data/uploads") +UPLOAD_DIR.mkdir(parents=True, exist_ok=True) + +logger = logging.getLogger(__name__) + + +# ============ 数据模型 ============ + + +class DigitalHuman(BaseModel): + """数字人信息""" + + id: str + name: str + desc: str + avatar_url: str | None = None + type: str = "preset" # preset, custom, upload + + +class VideoGenerateRequest(BaseModel): + """视频生成请求""" + + project_id: str = Field(..., description="项目ID") + human_id: int | None = Field(None, description="数字人主体ID(分镜类型使用)") + segments: list[Segment] = Field(..., description="分镜列表") + + +class VideoGenerateResponse(BaseModel): + """视频生成响应""" + + job_id: str = Field(..., description="作业ID") + task_id: str = Field(..., description="任务ID(与job_id相同)") + status: str = Field(..., description="作业状态") + message: str = Field(..., description="状态消息") + sse_url: str = Field(..., description="SSE进度流URL") + + +class VideoJobStatus(BaseModel): + """视频作业状态""" + + job_id: str + project_id: str + status: str # pending, processing, completed, partial, failed + progress: int + total_segments: int + completed_segments: int + failed_segments: int + created_at: float + updated_at: float + error_message: str | None = None + + +class ShotResult(BaseModel): + """单个分镜结果""" + + segment_id: str + type: str + status: str + task_id: str | None = None + video_url: str | None = None + local_path: str | None = None + error_message: str | None = None + + +class VideoJobDetail(BaseModel): + """视频作业详情""" + + job_id: str + project_id: str + status: str + progress: int + total_segments: int + completed_segments: int + failed_segments: int + segments: list[ShotResult] + created_at: float + updated_at: float + + +# ============ 内存存储 ============ + +# 数字人库 +digital_humans_db: dict[str, DigitalHuman] = { + "dh_001": DigitalHuman( + id="dh_001", + name="商务男士", + desc="专业稳重的商务形象,适合正式场合", + type="preset", + ), + "dh_002": DigitalHuman( + id="dh_002", + name="亲和女士", + desc="温和亲切的女性形象,适合讲解分享", + type="preset", + ), + "dh_003": DigitalHuman( + id="dh_003", + name="活力青年", + desc="年轻有活力的形象,适合轻松内容", + type="preset", + ), + "dh_004": DigitalHuman( + id="dh_004", name="知性女性", desc="知性优雅的形象,适合知识分享", type="preset" + ), +} + +# ============ 辅助函数 ============ + + +async def get_klingai_provider() -> KlingAIProvider: + """获取 KlingAI Provider 实例 + + API Key 从 Settings 读取(符合配置规范) + """ + settings = get_settings() + config_loader = get_config_loader() + platform = config_loader.get_platform("klingai") + + # 从 Settings 读取 AK/SK(符合配置规范:.env → Settings → 服务层) + access_key = settings.KLINGAI_ACCESS_KEY + secret_key = settings.KLINGAI_SECRET_KEY + + if not access_key or not secret_key: + raise HTTPException( + status_code=400, + detail="KlingAI 未配置,请设置 KLINGAI_ACCESS_KEY 和 KLINGAI_SECRET_KEY", + ) + + # 从 YAML 读取 base_url(模型配置) + base_url = platform.base_url if platform else None + + return KlingAIProvider( + { + "access_key": access_key, + "secret_key": secret_key, + "base_url": base_url or "https://api-beijing.klingai.com", + } + ) + + +# ============ 新版 API 路由(推荐) ============ + + +@router.post("/generate", response_model=ApiResponse[VideoGenerateResponse]) +async def create_video_generation(data: VideoGenerateRequest): + """ + 创建视频生成任务 + + 接收项目ID、数字人ID和分镜列表,创建视频生成作业。 + 支持 SSE 流式查询进度。 + + **分镜类型说明:** + - `segment`: 分镜(带数字人),使用 omni-video 接口,需要 human_id + - `empty_shot`: 空镜,使用文生图 + 图生视频流程 + + **调用流程:** + 1. 调用此接口创建任务,获取 job_id + 2. 使用 SSE 接口 `/video/jobs/{job_id}/stream` 监听进度 + 3. 或使用 `/video/jobs/{job_id}` 查询状态 + """ + try: + service = get_kling_video_service() + + # 转换分镜数据 + segments_data = [] + for segment in data.segments: + segments_data.append( + { + "id": segment.id, + "type": segment.type, + "scene": segment.scene, + "voiceover": segment.voiceover, + "voice_id": segment.voice_id, + } + ) + + # 创建作业 + job = await service.create_job( + project_id=data.project_id, + human_id=data.human_id, + segments_data=segments_data, + ) + + # 构建SSE URL + sse_url = f"/video/jobs/{job.job_id}/stream" + + return success_response( + data=VideoGenerateResponse( + job_id=job.job_id, + task_id=job.job_id, + status=job.status, + message="视频生成任务已创建", + sse_url=sse_url, + ) + ) + + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + logger.error(f"创建视频生成任务失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/jobs/{job_id}", response_model=ApiResponse[VideoJobDetail]) +async def get_video_job(job_id: str): + """ + 查询视频生成作业详情 + + 获取指定作业的详细信息和所有分镜的处理结果。 + """ + try: + service = get_kling_video_service() + job = service.get_job(job_id) + + if not job: + raise HTTPException(status_code=404, detail="作业不存在") + + # 构建分镜结果 + segments = [] + for segment in job.segments: + segments.append( + ShotResult( + segment_id=segment.id, + type=segment.type, + status=segment.status, + task_id=segment.provider_task_id, + video_url=segment.video_url, + local_path=segment.local_path, + error_message=segment.error_message, + ) + ) + + return success_response( + data=VideoJobDetail( + job_id=job.job_id, + project_id=job.project_id, + status=job.status, + progress=job.progress, + total_segments=len(job.segments), + completed_segments=sum(1 for s in job.segments if s.status.value == "completed"), + failed_segments=sum(1 for s in job.segments if s.status.value == "failed"), + segments=segments, + created_at=job.created_at, + updated_at=job.updated_at, + ) + ) + + except HTTPException: + raise + except Exception as e: + logger.error(f"查询作业详情失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/jobs/{job_id}/stream") +async def stream_video_job(job_id: str): + """ + SSE 流式获取视频生成进度 + + 使用 Server-Sent Events 实时推送视频生成进度。 + + **事件类型:** + - `start`: 开始生成 + - `processing`: 处理中(包含进度信息) + - `finalizing`: 完成整理 + - `complete`: 全部完成 + - `error`: 发生错误 + + **示例:** + ``` + const eventSource = new EventSource('/api/v1/video/jobs/{job_id}/stream'); + eventSource.onmessage = (e) => { + const data = JSON.parse(e.data); + console.log(data.progress + '%: ' + data.message); + }; + ``` + """ + try: + service = get_kling_video_service() + + # 验证作业存在 + job = service.get_job(job_id) + if not job: + raise HTTPException(status_code=404, detail="作业不存在") + + async def event_generator(): + """SSE 事件生成器""" + async for event in service.process_job_stream(job_id): + yield f"data: {__import__('json').dumps(event, ensure_ascii=False)}\n\n" + + return StreamingResponse( + event_generator(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + }, + ) + + except HTTPException: + raise + except Exception as e: + logger.error(f"流式获取作业进度失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/jobs/{job_id}/status", response_model=ApiResponse[VideoJobStatus]) +async def get_video_job_status(job_id: str): + """ + 获取视频生成作业状态(简化版) + """ + try: + service = get_kling_video_service() + status = service.get_job_status(job_id) + + if not status: + raise HTTPException(status_code=404, detail="作业不存在") + + return success_response( + data=VideoJobStatus( + job_id=str(status["job_id"]), + project_id=str(status["project_id"]), + status=str(status["status"]), + progress=int(status["progress"]), # type: ignore[arg-type] + total_segments=int(status["total_segments"]), # type: ignore[arg-type] + completed_segments=int(status["completed_segments"]), # type: ignore[arg-type] + failed_segments=int(status["failed_segments"]), # type: ignore[arg-type] + created_at=float(status["created_at"]), # type: ignore[arg-type] + updated_at=float(status["updated_at"]), # type: ignore[arg-type] + error_message=status.get("error_message"), + ) + ) + + except HTTPException: + raise + except Exception as e: + logger.error(f"获取作业状态失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +# ============ 数字人管理 ============ + + +@router.get("/library", response_model=ApiResponse[list[DigitalHuman]]) +async def get_digital_humans(): + """ + 获取数字人素材库 + + 返回系统预设的数字人列表。 + """ + try: + humans = list(digital_humans_db.values()) + return success_response(data=humans) + except Exception as e: + logger.error(f"获取数字人库失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/upload", response_model=ApiResponse[DigitalHuman]) +async def upload_video( + file: UploadFile = File(..., description="视频文件"), + name: str | None = Form(None, description="数字人名称"), +): + """ + 上传人物视频作为数字人素材 + + 文件要求: + - 格式:mp4, mov + - 时长:2-60秒 + - 分辨率:720p 或 1080p + """ + try: + # 验证文件格式 + allowed_types = ["video/mp4", "video/quicktime", "video/x-msvideo"] + if file.content_type not in allowed_types: + raise HTTPException( + status_code=400, + detail=f"不支持的文件格式: {file.content_type},请上传 mp4/mov 视频", + ) + + # 保存文件 + file_ext = Path(file.filename or "").suffix or ".mp4" + video_id = f"upload_{uuid.uuid4().hex[:16]}" + video_filename = f"{video_id}{file_ext}" + video_path = UPLOAD_DIR / video_filename + + content = await file.read() + video_path.write_bytes(content) + + logger.info(f"视频上传成功: {video_path}, 大小: {len(content)} bytes") + + # 创建数字人记录 + human = DigitalHuman( + id=video_id, + name=name or f"上传视频_{datetime.now().strftime('%m%d_%H%M')}", + desc="用户上传的自定义数字人", + type="upload", + avatar_url=f"/api/v1/video/{video_id}/thumbnail", + ) + + # 添加到数据库 + digital_humans_db[video_id] = human + + return success_response(data=human) + + except HTTPException: + raise + except Exception as e: + logger.error(f"上传视频失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/{video_id}/download") +async def download_video(video_id: str): + """ + 下载视频文件 + + 支持三种查找位置: + 1. data/video/{video_id}.mp4 - 传统存储 + 2. data/uploads/{video_id}.ext - 上传文件 + 3. ~/Documents/Meijiaka/projects/*/videos/{video_id}.mp4 - 项目生成的视频 + 文件名格式: scene_{shot_id}.mp4 + """ + try: + # 1. 首先查找传统存储位置 + video_path = VIDEO_STORAGE_DIR / f"{video_id}.mp4" + found = False + + if not video_path.exists(): + # 2. 尝试从上传目录查找 + for ext in [".mp4", ".mov", ".avi"]: + candidate = UPLOAD_DIR / f"{video_id}{ext}" + if candidate.exists(): + video_path = candidate + found = True + break + else: + found = True + + # 3. 如果还没找到,尝试在项目视频目录中查找 + # video_id 可能是 scene_{id} 格式 + if not found: + from app.services.kling_video_service import KlingVideoService + + # 遍历项目目录查找(递归查找) + base_dir = KlingVideoService.BASE_STORAGE_DIR + if base_dir.exists(): + for project_dir in base_dir.iterdir(): + if project_dir.is_dir(): + candidate = project_dir / "videos" / f"{video_id}.mp4" + if candidate.exists(): + video_path = candidate + found = True + break + + if not found or not video_path.exists(): + raise HTTPException(status_code=404, detail="视频文件不存在") + + return FileResponse(path=video_path, media_type="video/mp4", filename=f"{video_id}.mp4") + + except HTTPException: + raise + except Exception as e: + logger.error(f"下载视频失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/{video_id}/thumbnail") +async def get_video_thumbnail(video_id: str): + """ + 获取视频缩略图 + """ + try: + # 简化实现:返回占位图 + # 实际应该使用 FFmpeg 提取视频第一帧 + raise HTTPException(status_code=404, detail="缩略图功能暂未实现") + + except Exception as e: + logger.error(f"获取缩略图失败: {e}") + raise HTTPException(status_code=500, detail=str(e)) diff --git a/python-api/app/config.py b/python-api/app/config.py new file mode 100644 index 0000000..f3c9a17 --- /dev/null +++ b/python-api/app/config.py @@ -0,0 +1,208 @@ +""" +配置管理 - Pydantic Settings +========================== + +所有配置项通过环境变量或 .env 文件注入。 +""" + +from functools import lru_cache +from typing import Literal + +from pydantic import Field +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + """应用配置""" + + model_config = SettingsConfigDict( + env_file=".env", + env_file_encoding="utf-8", + extra="ignore", + arbitrary_types_allowed=True, + ) + + # 应用基础配置 + APP_NAME: str = Field(default="美家卡智影 API", description="应用名称") + APP_VERSION: str = Field(default="0.1.0", description="应用版本") + DEBUG: bool = Field(default=True, description="调试模式") + ENV: Literal["development", "staging", "production"] = Field( + default="development", description="运行环境" + ) + + # 服务器配置 + HOST: str = Field(default="0.0.0.0", description="监听地址") + PORT: int = Field(default=8000, description="监听端口") + WORKERS: int = Field(default=1, description="工作进程数(生产环境建议 > 1)") + + # 数据库配置(统一使用 PostgreSQL) + DATABASE_URL: str = Field( + default="postgresql+asyncpg://postgres:postgres@localhost:5432/meijiaka", + description="数据库连接字符串(PostgreSQL)", + ) + DATABASE_POOL_SIZE: int = Field(default=10, description="数据库连接池大小") + DATABASE_MAX_OVERFLOW: int = Field(default=20, description="连接池溢出上限") + + # Redis 配置 + REDIS_HOST: str = Field( + default="localhost", + description="Redis 主机地址", + ) + REDIS_PORT: int = Field( + default=6379, + description="Redis 端口", + ) + REDIS_DB: int = Field( + default=0, + description="Redis 数据库编号", + ) + REDIS_PASSWORD: str | None = Field( + default=None, + description="Redis 密码(无密码请留空)", + ) + + # 安全配置 + SECRET_KEY: str = Field( + default="your-secret-key-here-change-in-production", + description="JWT 签名密钥(生产环境必须修改)", + ) + ACCESS_TOKEN_EXPIRE_MINUTES: int = Field( + default=60 * 24 * 7, # 7 天 + description="访问令牌过期时间(分钟)", + ) + ALGORITHM: str = Field(default="HS256", description="JWT 算法") + + # CORS 配置 + CORS_ORIGINS: str = Field( + default="http://localhost:1420,http://127.0.0.1:1420,http://localhost:8080,http://127.0.0.1:8080", + description="允许的跨域来源(逗号分隔)", + ) + + # AI 模型配置 + # 字节跳动 - 火山方舟 + # 文档:https://www.volcengine.com/docs/82379/1399009 + VOLCENGINE_API_KEY: str | None = Field(default=None, description="火山方舟 API Key") + VOLCENGINE_BASE_URL: str = Field( + default="https://ark.cn-beijing.volces.com/api/v3", + description="火山方舟 Base URL", + ) + VOLCENGINE_MODEL: str = Field( + default="doubao-seed-2-0-lite-260215", + description="火山方舟默认模型(Model ID)", + ) + + # 火山引擎音视频字幕服务 + VOLCENGINE_CAPTION_APPID: str | None = Field(default=None, description="火山字幕 AppID") + VOLCENGINE_CAPTION_TOKEN: str | None = Field(default=None, description="火山字幕 Token") + + # OpenAI + OPENAI_API_KEY: str | None = Field(default=None, description="OpenAI API Key") + OPENAI_BASE_URL: str = Field(default="https://api.openai.com/v1", description="OpenAI Base URL") + OPENAI_DEFAULT_MODEL: str = Field(default="gpt-3.5-turbo", description="默认 OpenAI 模型") + + # 文心一言 (百度) + WENXIN_API_KEY: str | None = Field(default=None, description="文心一言 API Key") + WENXIN_SECRET_KEY: str | None = Field(default=None, description="文心一言 Secret Key") + + # 通义千问 (阿里云) + QIANWEN_API_KEY: str | None = Field(default=None, description="通义千问 API Key") + + # 数字人服务配置 + DIGITAL_HUMAN_PROVIDER: Literal["heygen", "did", "mock"] = Field( + default="mock", + description="数字人服务提供商", + ) + HEYGEN_API_KEY: str | None = Field(default=None, description="HeyGen API Key") + DID_API_KEY: str | None = Field(default=None, description="D-ID API Key") + + # KlingAI 配置 + KLINGAI_ACCESS_KEY: str | None = Field(default=None, description="KlingAI Access Key") + KLINGAI_SECRET_KEY: str | None = Field(default=None, description="KlingAI Secret Key") + + # 七牛云存储配置 + QINIU_ACCESS_KEY: str | None = Field(default=None, description="七牛云 Access Key") + QINIU_SECRET_KEY: str | None = Field(default=None, description="七牛云 Secret Key") + QINIU_VIDEO_BUCKET: str = Field(default="media-liche", description="视频存储 Bucket") + QINIU_VIDEO_DOMAIN: str = Field(default="media.liche.cn", description="视频存储域名") + QINIU_IMAGE_BUCKET: str = Field(default="img-liche", description="图片存储 Bucket") + QINIU_IMAGE_DOMAIN: str = Field(default="img.liche.cn", description="图片存储域名") + + # AnyToCopy 文案提取服务 + ANYTOCOPY_API_KEY: str | None = Field(default=None, description="AnyToCopy API Key") + ANYTOCOPY_API_SECRET: str | None = Field(default=None, description="AnyToCopy API Secret") + ANYTOCOPY_BASE_URL: str = Field( + default="https://api.anytocopy.com/vip/open-api/v1", + description="AnyToCopy Base URL", + ) + + # 视频生成配置 + DEFAULT_EMPTY_SHOT_VOICE_ID: str = Field( + default="829826792415842333", + description="空镜视频默认音色ID(Kling官方音色,默认:播报男声)", + ) + + # Async Engine 槽位配置 + KLING_VIDEO_MAX_CONCURRENT: int = Field(default=18, description="Kling视频生成最大并发数") + KLING_IMAGE_MAX_CONCURRENT: int = Field(default=9, description="Kling图片生成最大并发数") + KLING_AVATAR_MAX_CONCURRENT: int = Field(default=2, description="Kling形象克隆最大并发数") + ANYTOCOPY_MAX_CONCURRENT: int = Field(default=5, description="AnyToCopy文案提取最大并发数") + VOLC_SUBTITLE_MAX_CONCURRENT: int = Field(default=5, description="火山字幕生成最大并发数") + + # 任务超时配置(秒) + KLING_VIDEO_TIMEOUT_PER_SHOT: int = Field( + default=600, description="Kling视频单镜头超时时间(秒)" + ) + KLING_IMAGE_TIMEOUT: int = Field(default=120, description="Kling图片生成超时时间(秒)") + VOLC_SUBTITLE_TIMEOUT: int = Field(default=600, description="火山字幕生成超时时间(秒)") + + # AnyToCopy 轮询配置 + ANYTOCOPY_POLL_INTERVAL: float = Field(default=3.0, description="AnyToCopy轮询间隔(秒)") + ANYTOCOPY_MAX_POLL: int = Field(default=60, description="AnyToCopy最大轮询次数") + + # 日志配置 + LOG_LEVEL: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = Field( + default="DEBUG", + description="日志级别", + ) + + @property + def cors_origins_list(self) -> list[str]: + """将 CORS_ORIGINS 字符串解析为列表""" + return [origin.strip() for origin in self.CORS_ORIGINS.split(",")] + + @property + def use_redis(self) -> bool: + """是否使用 Redis""" + return bool(self.REDIS_HOST) + + +@lru_cache +def get_settings() -> Settings: + """获取配置单例(带缓存)""" + settings = Settings() + + # 生产环境安全检查 + if settings.ENV == "production": + default_keys = [ + "your-secret-key-here-change-in-production", + "change-me-in-production", + "secret-key", + "", + ] + if not settings.SECRET_KEY or settings.SECRET_KEY in default_keys: + raise ValueError( + "生产环境必须设置强随机 SECRET_KEY!" + "请在 .env 文件中设置一个随机字符串(至少 32 位)。" + ) + + # 检查 CORS 配置 + if settings.CORS_ORIGINS and "localhost" in settings.CORS_ORIGINS.lower(): + import warnings + + warnings.warn( + "生产环境 CORS 配置中包含 localhost,建议限制为实际域名", + RuntimeWarning, + stacklevel=2, + ) + + return settings diff --git a/python-api/app/core/__init__.py b/python-api/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python-api/app/core/config_loader.py b/python-api/app/core/config_loader.py new file mode 100644 index 0000000..77fe2cc --- /dev/null +++ b/python-api/app/core/config_loader.py @@ -0,0 +1,231 @@ +""" +AI 模型配置加载器 +================ + +从 YAML 文件加载模型配置,支持热重载。 +API Key 从 Settings 读取(符合配置规范)。 +""" + +import logging +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +logger = logging.getLogger(__name__) + +# 尝试导入 YAML 库 +try: + import yaml + + YAML_AVAILABLE = True +except ImportError: + YAML_AVAILABLE = False + logger.warning("PyYAML 未安装,使用 JSON 备选方案。安装: pip install pyyaml") + + +@dataclass +class PlatformConfig: + """平台配置""" + + id: str + name: str + provider: str + priority: int = 100 + base_url: str = "" # 从 YAML 读取,可选 + + +@dataclass +class ModelConfig: + """模型配置""" + + id: str + platform_id: str + model_name: str + display_name: str + capabilities: list[str] = field(default_factory=list) + default_params: dict[str, Any] = field(default_factory=dict) + is_enabled: bool = True + cost_per_1k_input: float = 0.0 + cost_per_1k_output: float = 0.0 + max_tokens_limit: int = 4096 + + +class AIModelConfigLoader: + """AI 模型配置加载器 + + 从 YAML 加载模型配置(支持热重载)。 + API Key 从 Settings 读取(通过 get_settings()),符合配置规范。 + """ + + DEFAULT_CONFIG_PATH = ( + Path(__file__).parent.parent.parent / "config" / "ai_models.yaml" + ) + + def __init__(self, config_path: str | None = None): + self.config_path = ( + Path(config_path) if config_path else self.DEFAULT_CONFIG_PATH + ) + self._platforms: dict[str, PlatformConfig] = {} + self._models: dict[str, ModelConfig] = {} + self._task_defaults: dict[str, str] = {} + self._last_modified = 0 + self._load() + + def _load(self): + """加载配置文件""" + if not self.config_path.exists(): + logger.warning(f"配置文件不存在: {self.config_path},使用默认配置") + self._load_defaults() + return + + try: + with open(self.config_path, encoding="utf-8") as f: + if YAML_AVAILABLE: + config = yaml.safe_load(f) + else: + # 备选:使用 JSON + import json + + config = json.load(f) + + self._parse_config(config) + self._last_modified = self.config_path.stat().st_mtime + logger.info( + f"已加载模型配置: {len(self._platforms)} 平台, {len(self._models)} 模型" + ) + + except Exception as e: + logger.error(f"加载配置文件失败: {e},使用默认配置") + self._load_defaults() + + def _parse_config(self, config: dict): + """解析配置(仅解析模型配置,API Key 从 Settings 读取)""" + # 解析平台 + platforms_data = config.get("platforms", {}) + for pid, pdata in platforms_data.items(): + self._platforms[pid] = PlatformConfig( + id=pid, + name=pdata.get("name", pid), + provider=pdata.get("provider", pid), + priority=pdata.get("priority", 100), + base_url=pdata.get("base_url", ""), + ) + + # 解析模型 + models_data = config.get("models", {}) + for mid, mdata in models_data.items(): + self._models[mid] = ModelConfig( + id=mid, + platform_id=mdata.get("platform_id", ""), + model_name=mdata.get("model_name", mid), + display_name=mdata.get("display_name", mid), + capabilities=mdata.get("capabilities", []), + default_params=mdata.get("default_params", {}), + is_enabled=mdata.get("is_enabled", True), + cost_per_1k_input=mdata.get("cost_per_1k_input", 0.0), + cost_per_1k_output=mdata.get("cost_per_1k_output", 0.0), + max_tokens_limit=mdata.get("max_tokens_limit", 4096), + ) + + # 解析任务默认映射 + self._task_defaults = config.get("task_defaults", {}) + + def _load_defaults(self): + """加载默认配置""" + self._platforms = { + "mock": PlatformConfig( + id="mock", + name="Mock 测试平台", + provider="mock", + priority=999, + ) + } + self._models = { + "mock-model": ModelConfig( + id="mock-model", + platform_id="mock", + model_name="mock-model", + display_name="Mock 测试模型", + capabilities=["script", "polish", "chat"], + ) + } + self._task_defaults = { + "script": "mock-model", + "polish": "mock-model", + "chat": "mock-model", + } + + def reload(self): + """重新加载配置(如果文件有更新)""" + if self.config_path.exists(): + current_mtime = self.config_path.stat().st_mtime + if current_mtime > self._last_modified: + logger.info("配置文件已更新,重新加载") + self._load() + return True + return False + + # ============== 查询方法 ============== + + def get_platform(self, platform_id: str) -> PlatformConfig | None: + """获取平台配置""" + return self._platforms.get(platform_id) + + def get_all_platforms(self) -> list[PlatformConfig]: + """获取所有平台(按优先级排序)""" + return sorted(self._platforms.values(), key=lambda p: p.priority) + + def get_model(self, model_id: str) -> ModelConfig | None: + """获取模型配置""" + return self._models.get(model_id) + + def get_all_models(self) -> list[ModelConfig]: + """获取所有模型""" + return list(self._models.values()) + + def get_enabled_models(self) -> list[ModelConfig]: + """获取启用的模型""" + return [m for m in self._models.values() if m.is_enabled] + + def get_models_by_capability(self, capability: str) -> list[ModelConfig]: + """根据能力获取模型""" + return [ + m + for m in self._models.values() + if m.is_enabled and capability in m.capabilities + ] + + def get_models_by_platform(self, platform_id: str) -> list[ModelConfig]: + """根据平台获取模型""" + return [ + m + for m in self._models.values() + if m.platform_id == platform_id and m.is_enabled + ] + + def get_default_model_for_task(self, task_type: str) -> str | None: + """获取任务类型的默认模型 ID""" + return self._task_defaults.get(task_type) + + def set_default_model_for_task(self, task_type: str, model_id: str): + """设置任务类型的默认模型(内存中,不保存到文件)""" + if model_id in self._models: + self._task_defaults[task_type] = model_id + + +# 全局配置加载器实例 +_config_loader: AIModelConfigLoader | None = None + + +def get_config_loader() -> AIModelConfigLoader: + """获取全局配置加载器""" + global _config_loader + if _config_loader is None: + _config_loader = AIModelConfigLoader() + return _config_loader + + +def reload_config() -> bool: + """重新加载配置""" + loader = get_config_loader() + return loader.reload() diff --git a/python-api/app/core/exceptions.py b/python-api/app/core/exceptions.py new file mode 100644 index 0000000..22b9f2e --- /dev/null +++ b/python-api/app/core/exceptions.py @@ -0,0 +1,89 @@ +""" +自定义异常类 +============ +""" + +from fastapi import HTTPException, status + + +class AppException(HTTPException): + """应用基础异常""" + + def __init__( + self, + status_code: int, + message: str = "操作失败", + detail: dict | None = None, + ): + super().__init__(status_code=status_code, detail=detail or {}) + self.message = message + + +class NotFoundException(AppException): + """资源不存在""" + + def __init__(self, message: str = "资源不存在"): + super().__init__( + status_code=status.HTTP_404_NOT_FOUND, + message=message, + ) + + +class ValidationException(AppException): + """参数验证失败""" + + def __init__(self, message: str = "参数验证失败"): + super().__init__( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + message=message, + ) + + +class UnauthorizedException(AppException): + """未授权""" + + def __init__(self, message: str = "未授权,请先登录"): + super().__init__( + status_code=status.HTTP_401_UNAUTHORIZED, + message=message, + ) + + +class ForbiddenException(AppException): + """禁止访问""" + + def __init__(self, message: str = "无权访问该资源"): + super().__init__( + status_code=status.HTTP_403_FORBIDDEN, + message=message, + ) + + +class BusinessException(AppException): + """业务逻辑错误""" + + def __init__(self, message: str = "业务操作失败"): + super().__init__( + status_code=status.HTTP_400_BAD_REQUEST, + message=message, + ) + + +class ModelUnavailableException(AppException): + """AI 模型不可用""" + + def __init__(self, message: str = "AI 模型服务暂时不可用"): + super().__init__( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + message=message, + ) + + +class TaskFailedException(AppException): + """异步任务执行失败""" + + def __init__(self, message: str = "任务执行失败"): + super().__init__( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + message=message, + ) diff --git a/python-api/app/core/redis_client.py b/python-api/app/core/redis_client.py new file mode 100644 index 0000000..819f10d --- /dev/null +++ b/python-api/app/core/redis_client.py @@ -0,0 +1,41 @@ +""" +Redis 客户端 +============ +全局 Redis 连接,供 Scheduler 和 RateLimiter 使用 +""" + +from redis.asyncio import Redis + +from app.config import get_settings + +# 全局客户端(懒加载) +_redis_client: Redis | None = None + + +def get_redis_client() -> Redis: + """获取或创建 Redis 客户端""" + global _redis_client + if _redis_client is None: + settings = get_settings() + + # 构建连接参数 + client_kwargs = { + "host": settings.REDIS_HOST, + "port": settings.REDIS_PORT, + "db": settings.REDIS_DB, + "decode_responses": True, + } + + # 有密码时添加 + if settings.REDIS_PASSWORD: + client_kwargs["password"] = settings.REDIS_PASSWORD + + _redis_client = Redis(**client_kwargs) + + return _redis_client + + +def init_redis_client(redis: Redis) -> None: + """初始化全局客户端(用于测试)""" + global _redis_client + _redis_client = redis diff --git a/python-api/app/core/security.py b/python-api/app/core/security.py new file mode 100644 index 0000000..98cf984 --- /dev/null +++ b/python-api/app/core/security.py @@ -0,0 +1,65 @@ +""" +安全工具 - JWT Token 生成与验证 +=============================== +""" + +from __future__ import annotations + +from datetime import UTC, datetime, timedelta +from typing import Any + +from jose import JWTError, jwt + +from app.config import get_settings + +settings = get_settings() + + +def create_access_token(data: dict[str, Any], expires_delta: timedelta | None = None) -> str: + """ + 创建 JWT 访问令牌 + + Args: + data: 要编码到 Token 中的数据(通常包含 user_id) + expires_delta: 过期时间偏移量,默认使用配置中的设置 + + Returns: + JWT Token 字符串 + """ + to_encode = data.copy() + + if expires_delta: + expire = datetime.now(UTC) + expires_delta + else: + expire = datetime.now(UTC) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + + to_encode.update({"exp": expire}) + + encoded_jwt = jwt.encode( + to_encode, + settings.SECRET_KEY, + algorithm=settings.ALGORITHM, + ) + + return encoded_jwt + + +def verify_token(token: str) -> dict[str, Any | None]: + """ + 验证 JWT Token + + Args: + token: JWT Token 字符串 + + Returns: + 解码后的 payload,如果验证失败返回 None + """ + try: + payload = jwt.decode( + token, + settings.SECRET_KEY, + algorithms=[settings.ALGORITHM], + ) + return payload + except JWTError: + return None diff --git a/python-api/app/core/token_manager.py b/python-api/app/core/token_manager.py new file mode 100644 index 0000000..51d27cf --- /dev/null +++ b/python-api/app/core/token_manager.py @@ -0,0 +1,436 @@ +""" +Token 管理器 - 通用 API 认证 Token 缓存与自动刷新 + +支持: +- JWT Token(如 KlingAI) +- OAuth2 Access Token +- 自定义 Token 类型 + +特性: +- 线程/协程安全的 token 缓存 +- 自动刷新(带安全边界) +- 后台预热机制 +- 支持多 Provider 实例隔离 +""" + +from __future__ import annotations + +import asyncio +import logging +import time +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from typing import Any, Protocol + +logger = logging.getLogger(__name__) + + +@dataclass +class TokenInfo: + """Token 信息容器""" + + token: str + expires_at: float # 过期时间戳(秒) + token_type: str = "Bearer" + extra_data: dict[str, Any] = field(default_factory=dict) + + @property + def is_expired(self) -> bool: + """是否已过期""" + return time.time() >= self.expires_at + + @property + def expires_in(self) -> float: + """剩余有效时间(秒)""" + return max(0, self.expires_at - time.time()) + + def is_near_expiry(self, safety_margin: float = 300) -> bool: + """ + 是否接近过期(需要刷新) + + Args: + safety_margin: 安全边界(秒),默认5分钟 + """ + return time.time() >= (self.expires_at - safety_margin) + + +class TokenGenerator(Protocol): + """Token 生成函数协议""" + + async def __call__(self) -> TokenInfo: + """生成/获取新的 token""" + ... + + +class BaseTokenStrategy(ABC): + """Token 生成策略基类""" + + @abstractmethod + async def generate(self) -> TokenInfo: + """生成新的 token""" + pass + + @abstractmethod + def get_cache_key(self) -> str: + """获取缓存标识(用于多实例隔离)""" + pass + + +class JWTTokenStrategy(BaseTokenStrategy): + """JWT Token 生成策略(用于 KlingAI 等)""" + + def __init__( + self, + access_key: str, + secret_key: str, + expires_in: int = 1800, + algorithm: str = "HS256", + token_type: str = "JWT", + ): + self.access_key = access_key + self.secret_key = secret_key + self.expires_in = expires_in # 默认30分钟 + self.algorithm = algorithm + self.token_type = token_type + + async def generate(self) -> TokenInfo: + """生成 JWT Token""" + from jose import jwt + + headers = {"alg": self.algorithm, "typ": self.token_type} + current_time = int(time.time()) + payload = { + "iss": self.access_key, + "exp": current_time + self.expires_in, + "nbf": current_time - 5, + } + + token = jwt.encode(payload, self.secret_key, algorithm=self.algorithm, headers=headers) + + return TokenInfo( + token=token, + expires_at=current_time + self.expires_in, + token_type="Bearer", + ) + + def get_cache_key(self) -> str: + """缓存标识:access_key 的 hash""" + return f"jwt:{self.access_key[:8]}" + + +class OAuth2TokenStrategy(BaseTokenStrategy): + """OAuth2 Token 生成策略""" + + def __init__( + self, + client_id: str, + client_secret: str, + token_url: str, + scope: str | None = None, + extra_params: dict[str, Any] | None = None, + ): + self.client_id = client_id + self.client_secret = client_secret + self.token_url = token_url + self.scope = scope + self.extra_params = extra_params or {} + + async def generate(self) -> TokenInfo: + """从 OAuth2 服务器获取 token""" + import httpx + + data = { + "grant_type": "client_credentials", + "client_id": self.client_id, + "client_secret": self.client_secret, + **self.extra_params, + } + if self.scope: + data["scope"] = self.scope + + async with httpx.AsyncClient() as client: + response = await client.post(self.token_url, data=data) + response.raise_for_status() + result = response.json() + + access_token = result["access_token"] + expires_in = result.get("expires_in", 3600) + token_type = result.get("token_type", "Bearer") + + return TokenInfo( + token=access_token, + expires_at=time.time() + expires_in, + token_type=token_type, + extra_data={ + k: v + for k, v in result.items() + if k not in ["access_token", "expires_in", "token_type"] + }, + ) + + def get_cache_key(self) -> str: + """缓存标识:client_id + token_url 的 hash""" + return f"oauth2:{self.client_id[:8]}:{hash(self.token_url) % 10000}" + + +class TokenManager: + """ + Token 管理器 - 单例模式,全局统一管理所有 token + + 使用示例: + # JWT 方式(KlingAI) + strategy = JWTTokenStrategy(access_key="xxx", secret_key="yyy") + token = await TokenManager.get_instance().get_token(strategy) + + # OAuth2 方式 + strategy = OAuth2TokenStrategy( + client_id="xxx", + client_secret="yyy", + token_url="https://api.example.com/oauth2/token" + ) + token = await TokenManager.get_instance().get_token(strategy) + """ + + _instance: TokenManager | None = None + _lock: asyncio.Lock | None = None + + def __new__(cls) -> TokenManager: + if cls._instance is None: + cls._instance = super().__new__(cls) + cls._instance._initialized = False + return cls._instance + + @classmethod + def get_instance(cls) -> TokenManager: + """获取单例实例""" + if cls._instance is None: + cls._instance = cls() + return cls._instance + + def __init__(self): + if self._initialized: + return + + # token 缓存: {cache_key: TokenInfo} + self._tokens: dict[str, TokenInfo] = {} + + # 刷新锁: {cache_key: asyncio.Lock} + self._refresh_locks: dict[str, asyncio.Lock] = {} + + # 全局锁,用于创建新的 refresh_lock + self._global_lock = asyncio.Lock() + + # 后台刷新任务 + self._background_tasks: set[asyncio.Task] = set() + + # 预热配置 + self._safety_margin = 300 # 提前5分钟刷新 + self._preemptive_refresh = True # 启用预热机制 + + self._initialized = True + + async def get_token( + self, + strategy: BaseTokenStrategy, + force_refresh: bool = False, + ) -> TokenInfo: + """ + 获取有效的 token + + Args: + strategy: Token 生成策略 + force_refresh: 强制刷新(忽略缓存) + + Returns: + TokenInfo: 有效的 token 信息 + """ + cache_key = strategy.get_cache_key() + + # 检查缓存 + if not force_refresh and cache_key in self._tokens: + token_info = self._tokens[cache_key] + if not token_info.is_near_expiry(self._safety_margin): + logger.debug(f"Token cache hit for {cache_key}") + return token_info + + # 需要刷新 token + return await self._refresh_token(strategy) + + async def get_token_string( + self, + strategy: BaseTokenStrategy, + force_refresh: bool = False, + ) -> str: + """ + 获取 token 字符串(快捷方法) + + Returns: + str: token 字符串(带 Bearer 前缀) + """ + token_info = await self.get_token(strategy, force_refresh) + return f"{token_info.token_type} {token_info.token}" + + async def _refresh_token(self, strategy: BaseTokenStrategy) -> TokenInfo: + """ + 刷新 token(带并发控制) + + 使用双重检查锁定模式,确保并发请求只触发一次刷新 + """ + cache_key = strategy.get_cache_key() + + # 获取或创建该 cache_key 专用的刷新锁 + async with self._global_lock: + if cache_key not in self._refresh_locks: + self._refresh_locks[cache_key] = asyncio.Lock() + + refresh_lock = self._refresh_locks[cache_key] + + async with refresh_lock: + # 双重检查:等待锁之后,可能其他协程已经刷新过了 + if cache_key in self._tokens: + token_info = self._tokens[cache_key] + if not token_info.is_near_expiry(self._safety_margin): + logger.debug(f"Token refreshed by another task for {cache_key}") + return token_info + + # 执行刷新 + logger.info(f"Refreshing token for {cache_key}") + try: + new_token = await strategy.generate() + self._tokens[cache_key] = new_token + + # 启动后台预热任务 + if self._preemptive_refresh: + self._schedule_preemptive_refresh(strategy, new_token) + + logger.info( + f"Token refreshed successfully for {cache_key}, expires in {new_token.expires_in:.0f}s" + ) + return new_token + + except Exception as e: + logger.error(f"Failed to refresh token for {cache_key}: {e}") + # 如果刷新失败但缓存的 token 还能用,返回缓存的 + if cache_key in self._tokens: + cached = self._tokens[cache_key] + if not cached.is_expired: + logger.warning( + f"Using expired cache for {cache_key} due to refresh failure" + ) + return cached + raise + + def _schedule_preemptive_refresh(self, strategy: BaseTokenStrategy, token_info: TokenInfo): + """ + 调度后台预热刷新任务 + + 在 token 即将过期前自动刷新,避免请求时等待 + """ + cache_key = strategy.get_cache_key() + + # 计算预热时间(token 过期前 safety_margin * 2) + refresh_at = token_info.expires_at - self._safety_margin * 2 + delay = max(0, refresh_at - time.time()) + + async def _refresh_task(): + await asyncio.sleep(delay) + try: + logger.info(f"Preemptive token refresh for {cache_key}") + await self._refresh_token(strategy) + except Exception as e: + logger.error(f"Preemptive refresh failed for {cache_key}: {e}") + + # 创建后台任务 + task = asyncio.create_task(_refresh_task()) + self._background_tasks.add(task) + task.add_done_callback(self._background_tasks.discard) + + logger.debug(f"Scheduled preemptive refresh for {cache_key} in {delay:.0f}s") + + async def invalidate(self, strategy: BaseTokenStrategy) -> bool: + """ + 使缓存失效 + + Returns: + bool: 是否成功删除 + """ + cache_key = strategy.get_cache_key() + if cache_key in self._tokens: + del self._tokens[cache_key] + logger.info(f"Token cache invalidated for {cache_key}") + return True + return False + + def clear(self): + """清除所有 token 缓存""" + self._tokens.clear() + logger.info("All token caches cleared") + + def get_stats(self) -> dict[str, Any]: + """获取缓存统计信息""" + now = time.time() + stats = { + "total_cached": len(self._tokens), + "active_tasks": len(self._background_tasks), + "tokens": {}, + } + + for key, token_info in self._tokens.items(): + stats["tokens"][key] = { + "expires_in": token_info.expires_in, + "is_expired": token_info.is_expired, + "is_near_expiry": token_info.is_near_expiry(self._safety_margin), + } + + return stats + + +# 便捷函数 + + +async def get_jwt_token( + access_key: str, + secret_key: str, + expires_in: int = 1800, + algorithm: str = "HS256", +) -> TokenInfo: + """ + 获取 JWT Token(使用全局 TokenManager) + + 示例: + token_info = await get_jwt_token("access_key", "secret_key") + headers = {"Authorization": f"Bearer {token_info.token}"} + """ + strategy = JWTTokenStrategy( + access_key=access_key, + secret_key=secret_key, + expires_in=expires_in, + algorithm=algorithm, + ) + return await TokenManager.get_instance().get_token(strategy) + + +async def get_oauth2_token( + client_id: str, + client_secret: str, + token_url: str, + scope: str | None = None, +) -> TokenInfo: + """ + 获取 OAuth2 Token(使用全局 TokenManager) + + 示例: + token_info = await get_oauth2_token( + client_id="xxx", + client_secret="yyy", + token_url="https://api.example.com/oauth2/token" + ) + headers = {"Authorization": f"Bearer {token_info.token}"} + """ + strategy = OAuth2TokenStrategy( + client_id=client_id, + client_secret=client_secret, + token_url=token_url, + scope=scope, + ) + return await TokenManager.get_instance().get_token(strategy) diff --git a/python-api/app/core/token_manager_example.py b/python-api/app/core/token_manager_example.py new file mode 100644 index 0000000..7c95586 --- /dev/null +++ b/python-api/app/core/token_manager_example.py @@ -0,0 +1,170 @@ +""" +TokenManager 使用示例 + +展示如何在 Provider 中使用 TokenManager 来管理认证 Token。 +""" + +import asyncio +import logging + +from app.core.token_manager import ( + JWTTokenStrategy, + OAuth2TokenStrategy, + TokenManager, + get_jwt_token, +) + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +async def example_jwt(): + """JWT Token 示例(KlingAI 模式)""" + print("=" * 60) + print("JWT Token 示例 (KlingAI)") + print("=" * 60) + + # 方法1: 使用便捷函数(推荐简单场景) + try: + token_info = await get_jwt_token( + access_key="test_access_key", + secret_key="test_secret_key", + ) + print(f"Token: {token_info.token[:50]}...") + print(f"Expires in: {token_info.expires_in:.0f} seconds") + print(f"Is expired: {token_info.is_expired}") + except Exception as e: + print(f"JWT generation failed (expected in demo): {e}") + + # 方法2: 使用 TokenManager + Strategy(推荐 Provider 集成) + strategy = JWTTokenStrategy( + access_key="your_access_key", + secret_key="your_secret_key", + expires_in=1800, # 30分钟 + ) + + # 第一次获取会生成新 token + token1 = await TokenManager.get_instance().get_token(strategy) + print(f"\nFirst token: {token1.token[:30]}...") + + # 第二次获取会命中缓存(如果未过期) + token2 = await TokenManager.get_instance().get_token(strategy) + print(f"Second token: {token2.token[:30]}...") + print(f"Same token: {token1.token == token2.token}") + + # 查看缓存统计 + stats = TokenManager.get_instance().get_stats() + print(f"\nCache stats: {stats}") + + +async def example_oauth2(): + """OAuth2 Token 示例""" + print("\n" + "=" * 60) + print("OAuth2 Token 示例") + print("=" * 60) + + strategy = OAuth2TokenStrategy( + client_id="your_client_id", + client_secret="your_client_secret", + token_url="https://api.example.com/oauth2/token", + scope="read write", + ) + + print("OAuth2 strategy created") + print(f"Cache key: {strategy.get_cache_key()}") + + +async def example_provider_integration(): + """Provider 集成示例""" + print("\n" + "=" * 60) + print("Provider 集成示例") + print("=" * 60) + + # 这是一个模拟的 Provider 类 + class ExampleProvider: + def __init__(self, access_key: str, secret_key: str): + self.access_key = access_key + self.secret_key = secret_key + self._token_strategy = JWTTokenStrategy( + access_key=access_key, + secret_key=secret_key, + expires_in=1800, + ) + + async def _get_headers(self) -> dict[str, str]: + """获取带认证的请求头""" + token_info = await TokenManager.get_instance().get_token(self._token_strategy) + return { + "Authorization": f"Bearer {token_info.token}", + "Content-Type": "application/json", + } + + async def make_request(self): + """模拟 API 请求""" + headers = await self._get_headers() + print(f"Request headers: {headers}") + # 实际使用时: await session.post(url, headers=headers, ...) + + provider = ExampleProvider("access_key_123", "secret_key_456") + await provider.make_request() + + +async def example_concurrent_requests(): + """并发请求示例 - 测试 token 刷新时的并发安全""" + print("\n" + "=" * 60) + print("并发请求示例") + print("=" * 60) + + strategy = JWTTokenStrategy( + access_key="concurrent_test_key", + secret_key="concurrent_test_secret", + expires_in=1800, + ) + + async def request_task(task_id: int): + """模拟单个请求""" + token_info = await TokenManager.get_instance().get_token(strategy) + print(f"Task {task_id}: got token (expires in {token_info.expires_in:.0f}s)") + return token_info + + # 并发10个请求,应该只触发一次 token 生成 + print("Launching 10 concurrent requests...") + results = await asyncio.gather(*[request_task(i) for i in range(10)]) + + # 验证所有请求拿到的是同一个 token + tokens = [r.token for r in results] + unique_tokens = set(tokens) + print(f"\nTotal requests: {len(tokens)}") + print(f"Unique tokens generated: {len(unique_tokens)}") + print(f"Concurrent safety: {'✓ PASS' if len(unique_tokens) == 1 else '✗ FAIL'}") + + +async def example_stats(): + """查看 TokenManager 统计信息""" + print("\n" + "=" * 60) + print("TokenManager 统计") + print("=" * 60) + + manager = TokenManager.get_instance() + stats = manager.get_stats() + + print(f"Total cached tokens: {stats['total_cached']}") + print(f"Active background tasks: {stats['active_tasks']}") + print(f"Token details: {stats['tokens']}") + + +async def main(): + """运行所有示例""" + await example_jwt() + await example_oauth2() + await example_provider_integration() + await example_concurrent_requests() + await example_stats() + + print("\n" + "=" * 60) + print("所有示例完成") + print("=" * 60) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/python-api/app/crud/__init__.py b/python-api/app/crud/__init__.py new file mode 100644 index 0000000..fa5ca31 --- /dev/null +++ b/python-api/app/crud/__init__.py @@ -0,0 +1,19 @@ +""" +CRUD 模块 +======== + +统一导出所有 CRUD 实例,方便导入使用。 + +使用示例: + from app.crud import user + + user_obj = await user.get(db, id="xxx") +""" + +from app.crud.model_usage import model_usage_log +from app.crud.user import user + +__all__ = [ + "user", + "model_usage_log", +] diff --git a/python-api/app/crud/avatar.py b/python-api/app/crud/avatar.py new file mode 100644 index 0000000..7cdd769 --- /dev/null +++ b/python-api/app/crud/avatar.py @@ -0,0 +1,104 @@ +""" +Avatar CRUD 操作 +================ + +形象克隆记录的数据访问层。 +""" + +from datetime import UTC, datetime, timedelta + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.crud.base import CRUDBase +from app.models.avatar import Avatar +from app.schemas.avatar import AvatarCreate, AvatarUpdate + + +class CRUDAvatar(CRUDBase[Avatar, AvatarCreate, AvatarUpdate]): + """Avatar 数据访问对象""" + + def __init__(self) -> None: + super().__init__(Avatar) + + async def get_multi_by_user( + self, db: AsyncSession, *, user_id: str, skip: int = 0, limit: int = 100 + ) -> list[Avatar]: + """获取用户的形象列表(排除已软删除)""" + result = await db.execute( + select(Avatar) + .where(Avatar.user_id == user_id) + .where(Avatar.deleted_at.is_(None)) + .offset(skip) + .limit(limit) + .order_by(Avatar.created_at.desc()) + ) + return list(result.scalars().all()) + + async def soft_delete(self, db: AsyncSession, *, id: str, commit: bool = True) -> Avatar | None: + """软删除形象记录""" + obj = await self.get(db, id) + if obj: + obj.deleted_at = datetime.now(UTC) + if commit: + await db.commit() + await db.refresh(obj) + else: + await db.flush() + return obj + + async def get_stuck_tasks( + self, + db: AsyncSession, + processing_statuses: list[str], + timeout_minutes: int = 30, + limit: int = 100, + ) -> list[Avatar]: + """获取卡住的任务(超过指定时间未更新的处理中任务) + + Args: + db: 数据库会话 + processing_statuses: 需要检查的处理中状态列表 + timeout_minutes: 超时时间(分钟) + limit: 最大返回数量 + """ + timeout_threshold = datetime.now(UTC) - timedelta(minutes=timeout_minutes) + + result = await db.execute( + select(Avatar) + .where(Avatar.status.in_(processing_statuses)) + .where(Avatar.deleted_at.is_(None)) + .where(Avatar.updated_at < timeout_threshold) + .limit(limit) + .order_by(Avatar.updated_at.asc()) + ) + return list(result.scalars().all()) + + async def get_by_status_in( + self, + db: AsyncSession, + statuses: list[str], + updated_before: datetime | None = None, + limit: int = 100, + ) -> list[Avatar]: + """根据状态列表查询任务 + + Args: + db: 数据库会话 + statuses: 状态列表 + updated_before: 更新时间早于该时间的记录 + limit: 最大返回数量 + """ + query = select(Avatar).where(Avatar.status.in_(statuses)).where(Avatar.deleted_at.is_(None)) + + if updated_before: + query = query.where(Avatar.updated_at < updated_before) + + query = query.limit(limit).order_by(Avatar.updated_at.asc()) + + result = await db.execute(query) + return list(result.scalars().all()) + + +# 全局单例 +avatar = CRUDAvatar() diff --git a/python-api/app/crud/base.py b/python-api/app/crud/base.py new file mode 100644 index 0000000..ddd3697 --- /dev/null +++ b/python-api/app/crud/base.py @@ -0,0 +1,127 @@ +""" +CRUD 基础类 +========== + +提供通用的数据访问方法,所有业务 CRUD 必须继承此类。 +""" + +from typing import Any, Generic, TypeVar + +from pydantic import BaseModel +from sqlalchemy import func, select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.base import BaseModel as AppBaseModel + +ModelType = TypeVar("ModelType", bound=AppBaseModel) +CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel, default=Any) +UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel, default=Any) + + +class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): + """ + 通用 CRUD 基类 + + 所有业务 CRUD 必须继承此类,确保接口统一。 + + 使用示例: + class UserCRUD(CRUDBase[User, UserCreate, UserUpdate]): + def __init__(self): + super().__init__(User) + + # 添加业务特定方法... + + user = UserCRUD() + """ + + def __init__(self, model: type[ModelType]): + """ + Args: + model: SQLAlchemy 模型类 + """ + self.model = model + + async def get(self, db: AsyncSession, id: str) -> ModelType | None: + """根据 ID 获取单个对象""" + result = await db.execute(select(self.model).where(self.model.id == id)) + return result.scalar_one_or_none() + + async def get_multi( + self, db: AsyncSession, *, skip: int = 0, limit: int = 100 + ) -> list[ModelType]: + """获取多个对象(分页)""" + result = await db.execute(select(self.model).offset(skip).limit(limit)) + return list(result.scalars().all()) + + async def create( + self, db: AsyncSession, *, obj_in: CreateSchemaType | dict[str, Any], commit: bool = True + ) -> ModelType: + """创建对象 + + Args: + db: 数据库会话 + obj_in: 对象数据(Pydantic 模型或字典) + commit: 是否自动提交(默认True)。如需在事务中批量操作,设为False由调用方控制提交 + """ + if isinstance(obj_in, BaseModel): + obj_in = obj_in.model_dump(exclude_unset=True) + db_obj = self.model(**obj_in) + db.add(db_obj) + if commit: + await db.commit() + await db.refresh(db_obj) + else: + # 不提交时刷新以获取默认值(如自增ID),但需在事务中 + await db.flush() + await db.refresh(db_obj) + return db_obj + + async def update( + self, + db: AsyncSession, + *, + db_obj: ModelType, + obj_in: UpdateSchemaType | dict[str, Any], + commit: bool = True, + ) -> ModelType: + """更新对象 + + Args: + db: 数据库会话 + db_obj: 数据库对象 + obj_in: 更新数据(Pydantic 模型或字典) + commit: 是否自动提交(默认True)。如需在事务中批量操作,设为False由调用方控制提交 + """ + if isinstance(obj_in, BaseModel): + update_data = obj_in.model_dump(exclude_unset=True) + else: + update_data = obj_in + for field, value in update_data.items(): + if hasattr(db_obj, field) and value is not None: + setattr(db_obj, field, value) + if commit: + await db.commit() + await db.refresh(db_obj) + else: + await db.flush() + return db_obj + + async def delete(self, db: AsyncSession, *, id: str, commit: bool = True) -> ModelType | None: + """删除对象 + + Args: + db: 数据库会话 + id: 对象ID + commit: 是否自动提交(默认True)。如需在事务中批量操作,设为False由调用方控制提交 + """ + obj = await self.get(db, id) + if obj: + await db.delete(obj) + if commit: + await db.commit() + return obj + + async def count(self, db: AsyncSession) -> int: + """统计总数""" + result = await db.execute(select(func.count(self.model.id))) + return result.scalar() or 0 diff --git a/python-api/app/crud/model_usage.py b/python-api/app/crud/model_usage.py new file mode 100644 index 0000000..66704c1 --- /dev/null +++ b/python-api/app/crud/model_usage.py @@ -0,0 +1,45 @@ +""" +模型使用日志 CRUD 操作 +====================== + +仅保留使用日志功能,模型配置已迁移到 YAML 文件。 +""" + +from sqlalchemy import func, select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.crud.base import CRUDBase +from app.models.model_usage import ModelUsageLog + + +class ModelUsageLogCRUD(CRUDBase[ModelUsageLog]): + """模型使用日志 CRUD""" + + def __init__(self) -> None: + super().__init__(ModelUsageLog) + + async def get_daily_cost(self, db: AsyncSession, *, date: str) -> float: + """获取某日总成本""" + result = await db.execute( + select(func.sum(ModelUsageLog.cost_cny)).where( + func.date(ModelUsageLog.created_at) == date + ) + ) + return result.scalar() or 0.0 + + async def get_by_user( + self, db: AsyncSession, *, user_id: str, skip: int = 0, limit: int = 100 + ) -> list[ModelUsageLog]: + """获取用户的使用日志""" + result = await db.execute( + select(ModelUsageLog) + .where(ModelUsageLog.user_id == user_id) + .order_by(ModelUsageLog.created_at.desc()) + .offset(skip) + .limit(limit) + ) + return list(result.scalars().all()) + + +# 导出实例 +model_usage_log = ModelUsageLogCRUD() diff --git a/python-api/app/crud/user.py b/python-api/app/crud/user.py new file mode 100644 index 0000000..a5bf3e9 --- /dev/null +++ b/python-api/app/crud/user.py @@ -0,0 +1,51 @@ +""" +用户 CRUD 操作 +============== + +用户认证相关的数据访问。 +""" + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.crud.base import CRUDBase +from app.models.user import User + + +class UserCRUD(CRUDBase[User]): + """用户数据访问对象""" + + def __init__(self) -> None: + super().__init__(User) + + async def get_by_mobile(self, db: AsyncSession, *, mobile: str) -> User | None: + """根据手机号获取用户""" + result = await db.execute(select(User).where(User.mobile == mobile)) + return result.scalar_one_or_none() + + async def get_or_create_by_mobile( + self, db: AsyncSession, *, mobile: str, nickname: str | None = None + ) -> User: + """ + 根据手机号获取或创建用户 + + Returns: + 已存在或新创建的用户 + """ + user = await self.get_by_mobile(db, mobile=mobile) + + if user is None: + # 创建新用户 + user = await self.create( + db, + obj_in={ + "mobile": mobile, + "nickname": nickname or f"用户_{mobile[-4:]}", + }, + ) + + return user + + +# 导出实例 +user = UserCRUD() diff --git a/python-api/app/db/__init__.py b/python-api/app/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python-api/app/db/session.py b/python-api/app/db/session.py new file mode 100644 index 0000000..7d903fe --- /dev/null +++ b/python-api/app/db/session.py @@ -0,0 +1,61 @@ +""" +SQLAlchemy 数据库配置 +==================== + +统一使用 PostgreSQL + 异步模式。 +""" + +from sqlalchemy.ext.asyncio import ( + AsyncSession, + async_sessionmaker, + create_async_engine, +) +from sqlalchemy.orm import declarative_base + +from app.config import get_settings + +Base = declarative_base() + +settings = get_settings() + +async_engine = create_async_engine( + settings.DATABASE_URL, + pool_size=settings.DATABASE_POOL_SIZE, + max_overflow=settings.DATABASE_MAX_OVERFLOW, + pool_pre_ping=True, + echo=settings.DEBUG, +) + +AsyncSessionLocal = async_sessionmaker( + async_engine, + class_=AsyncSession, + expire_on_commit=False, + autocommit=False, + autoflush=False, +) + + +async def get_db(): + """获取异步数据库 Session + + 注意:commit 由调用方(API层或Service层)控制,不在此自动提交 + """ + async with AsyncSessionLocal() as session: + try: + yield session + except Exception: + await session.rollback() + raise + finally: + await session.close() + + +async def init_db(): + """初始化数据库""" + async with async_engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + +async def close_db(): + """关闭数据库连接""" + await async_engine.dispose() diff --git a/python-api/app/main.py b/python-api/app/main.py new file mode 100644 index 0000000..a72b7fc --- /dev/null +++ b/python-api/app/main.py @@ -0,0 +1,180 @@ +""" +FastAPI 应用入口 +================ +""" + +import logging +import sys +from contextlib import asynccontextmanager +from datetime import datetime +from pathlib import Path + +import uvicorn +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse + +from app.api.v1.router import api_router +from app.config import get_settings +from app.db.session import close_db, init_db +from app.schemas.common import ApiResponse + +settings = get_settings() + +# 配置日志 - 同时输出到控制台和文件 +log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +log_level = getattr(logging, settings.LOG_LEVEL) + +# 创建日志目录(在用户文档目录下) +log_dir = Path.home() / "Documents" / "Meijiaka" / "logs" +log_dir.mkdir(parents=True, exist_ok=True) + +# 日志文件名按日期 +log_file = log_dir / f"api_{datetime.now().strftime('%Y%m%d')}.log" + +# 配置根日志记录器 +logging.basicConfig( + level=log_level, + format=log_format, + handlers=[ + logging.StreamHandler(sys.stdout), # 控制台输出 + logging.FileHandler(log_file, encoding="utf-8", mode="a"), # 文件输出 + ], +) +logger = logging.getLogger(__name__) +logger.info(f"日志文件位置: {log_file}") + + +@asynccontextmanager +async def lifespan(app: FastAPI): + """ + 应用生命周期管理 + + - 启动时:初始化数据库、加载模型配置 + - 关闭时:清理资源 + """ + logger.info(f"Starting {settings.APP_NAME} v{settings.APP_VERSION}") + + # 开发环境自动创建表 + if settings.DEBUG and settings.ENV == "development": + logger.info("Initializing database tables...") + try: + # 确保所有模型已注册到 metadata + from app.models import Avatar, ModelUsageLog, User # noqa: F401 + + await init_db() + logger.info("Database tables initialized") + except Exception as e: + logger.warning(f"Database initialization skipped: {e}") + + # 加载 AI 模型配置(从 YAML 文件) + try: + from app.core.config_loader import get_config_loader + + config_loader = get_config_loader() + platforms_count = len(config_loader.get_all_platforms()) + models_count = len(config_loader.get_enabled_models()) + + logger.info(f"Loaded {platforms_count} platforms, {models_count} models from config file") + except Exception as e: + logger.warning(f"Failed to load models from config: {e}") + + yield + + # 关闭时清理 + logger.info("Shutting down...") + await close_db() + logger.info("Cleanup complete") + + +def create_app() -> FastAPI: + """创建 FastAPI 应用实例""" + + app = FastAPI( + title=settings.APP_NAME, + version=settings.APP_VERSION, + description="美家卡智影 - AI 视频创作后端 API", + docs_url="/docs" if settings.DEBUG else None, + redoc_url="/redoc" if settings.DEBUG else None, + lifespan=lifespan, + ) + + # CORS 配置 + # 开发环境下允许所有来源,避免跨域问题 + allow_origins = ["*"] if settings.DEBUG else settings.cors_origins_list + app.add_middleware( + CORSMiddleware, + allow_origins=allow_origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + + # 注册路由 + app.include_router(api_router, prefix="/api/v1") + + # 全局异常处理(统一返回 ApiResponse 格式) + @app.exception_handler(Exception) + async def global_exception_handler(request, exc): + """全局异常捕获""" + logger.exception("Unhandled exception") + return JSONResponse( + status_code=500, + content={ + "code": 500, + "message": "服务器内部错误", + "data": None, + "detail": {"error": str(exc)} if settings.DEBUG else None, + }, + ) + + # 健康检查 + @app.get("/health", tags=["System"]) + async def health_check(): + """服务健康检查""" + return ApiResponse( + code=200, + data={ + "status": "healthy", + "version": settings.APP_VERSION, + "environment": settings.ENV, + }, + message="服务运行正常", + ) + + # 根路由 + @app.get("/", tags=["System"]) + async def root(): + """API 根路径""" + return ApiResponse( + code=200, + data={ + "name": settings.APP_NAME, + "version": settings.APP_VERSION, + "docs": "/docs" if settings.DEBUG else None, + }, + message="美家卡智影 API 服务", + ) + + return app + + +# 创建应用实例 +app = create_app() + + +def main(): + """入口函数(用于命令行启动)""" + uvicorn.run( + "app.main:app", + host=settings.HOST, + port=settings.PORT, + workers=settings.WORKERS if not settings.DEBUG else 1, + reload=settings.DEBUG, + log_level=settings.LOG_LEVEL.lower(), + ) + + +if __name__ == "__main__": + main() +# test diff --git a/python-api/app/models/__init__.py b/python-api/app/models/__init__.py new file mode 100644 index 0000000..3d8893f --- /dev/null +++ b/python-api/app/models/__init__.py @@ -0,0 +1,20 @@ +""" +模型模块 + +所有 SQLAlchemy 模型定义。 + +注意:AIModel/AIPlatform 已迁移到 YAML 配置 (config/ai_models.yaml) +""" + +from app.models.avatar import Avatar +from app.models.base import BaseModel +from app.models.model_usage import ModelUsageLog +from app.models.user import User + +# 当前可用的模型 +__all__ = [ + "Avatar", + "BaseModel", + "ModelUsageLog", + "User", +] diff --git a/python-api/app/models/avatar.py b/python-api/app/models/avatar.py new file mode 100644 index 0000000..29610d9 --- /dev/null +++ b/python-api/app/models/avatar.py @@ -0,0 +1,140 @@ +""" +Avatar 形象克隆模型 +================== + +存储用户克隆形象的信息,作为本地 localStorage 的云端备份。 +""" + +from datetime import UTC, datetime + +from sqlalchemy import BigInteger, DateTime, ForeignKey, String, Text +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.session import Base +from app.schemas.enums import AvatarCloneStatus + + +class Avatar(Base): + """ + 形象克隆记录表 + + 用于备份用户在本地创建的克隆形象,支持换机恢复和客服排查。 + """ + + __tablename__ = "avatars" + + # 主键:本地生成的唯一标识(与 Kling element_id 无关) + id: Mapped[str] = mapped_column( + String(64), + primary_key=True, + comment="本地形象唯一标识(如 avt_xxx)", + ) + + # 关联用户(外键,对应 users.id) + user_id: Mapped[str] = mapped_column( + UUID(as_uuid=False), + ForeignKey("users.id", ondelete="CASCADE"), + nullable=False, + index=True, + comment="关联用户 ID", + ) + + # 形象展示名称 + name: Mapped[str] = mapped_column( + String(64), + nullable=False, + comment="形象展示名称", + ) + + # 供应商标识 + provider: Mapped[str] = mapped_column( + String(32), + nullable=False, + default="kling", + comment="供应商标识: kling", + ) + + # Kling 自定义音色 ID(创建成功后回填) + voice_id: Mapped[str | None] = mapped_column( + String(64), + nullable=True, + comment="Kling 自定义音色 ID", + ) + + # 供应商主体 ID(创建成功后回填,用于调用 omni-video API) + provider_element_id: Mapped[int | None] = mapped_column( + BigInteger, + nullable=True, + comment="供应商主体 ID(数字类型,调用 API 时使用)", + ) + + # 供应商任务 ID(用于客服追溯) + provider_voice_job_id: Mapped[str | None] = mapped_column( + String(128), + nullable=True, + index=True, + comment="供应商自定义音色任务 ID", + ) + + provider_element_job_id: Mapped[str | None] = mapped_column( + String(128), + nullable=True, + index=True, + comment="供应商主体创建任务 ID", + ) + + # 资源地址 + video_url: Mapped[str] = mapped_column( + Text, + nullable=False, + comment="原始人物视频 URL", + ) + + trial_url: Mapped[str | None] = mapped_column( + Text, + nullable=True, + comment="音色试听音频 URL", + ) + + # 状态机 + status: Mapped[str] = mapped_column( + String(32), + nullable=False, + default=AvatarCloneStatus.PENDING.value, + comment="状态: pending/voice_processing/voice_failed/element_processing/element_failed/succeed/timeout", + ) + + # 失败原因(用户可读) + fail_reason: Mapped[str | None] = mapped_column( + Text, + nullable=True, + comment="失败原因(中文可读)", + ) + + # 软删除标记 + deleted_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), + nullable=True, + comment="软删除时间,NULL 表示未删除", + ) + + # 时间戳 + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + default=lambda: datetime.now(UTC), + nullable=False, + comment="记录创建时间", + ) + + updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + default=lambda: datetime.now(UTC), + onupdate=lambda: datetime.now(UTC), + nullable=False, + comment="记录更新时间", + ) + + def to_dict(self) -> dict: + """转换为字典(用于序列化)""" + return {column.name: getattr(self, column.name) for column in self.__table__.columns} diff --git a/python-api/app/models/base.py b/python-api/app/models/base.py new file mode 100644 index 0000000..516bd1b --- /dev/null +++ b/python-api/app/models/base.py @@ -0,0 +1,49 @@ +""" +基础模型定义 +============ +""" + +import uuid +from datetime import UTC, datetime + +from sqlalchemy import DateTime +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.session import Base + + +class BaseModel(Base): + """ + 基础模型 - 所有模型继承此类 + + 提供: + - UUID 主键(自动生成) + - 创建时间 + - 更新时间 + """ + + __abstract__ = True + + id: Mapped[str] = mapped_column( + UUID(as_uuid=False), + primary_key=True, + default=lambda: str(uuid.uuid4()), + ) + + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + default=lambda: datetime.now(UTC), + nullable=False, + ) + + updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + default=lambda: datetime.now(UTC), + onupdate=lambda: datetime.now(UTC), + nullable=False, + ) + + def to_dict(self) -> dict: + """转换为字典(用于序列化)""" + return {column.name: getattr(self, column.name) for column in self.__table__.columns} diff --git a/python-api/app/models/model_usage.py b/python-api/app/models/model_usage.py new file mode 100644 index 0000000..ec401c1 --- /dev/null +++ b/python-api/app/models/model_usage.py @@ -0,0 +1,62 @@ +""" +AI 模型使用日志模型 +================== + +存储模型调用的使用日志,用于成本统计和监控。 + +模型配置已迁移到 YAML 文件:config/ai_models.yaml +""" + +from datetime import datetime + +from sqlalchemy import Boolean, Column, DateTime, Float, Index, Integer, String, Text + +from app.db.session import Base + + +class ModelUsageLog(Base): + """模型使用日志 - 用于成本统计和监控""" + + __tablename__ = "model_usage_logs" + + id = Column(Integer, primary_key=True, autoincrement=True) + + # 调用信息 + model_id = Column(String(100), nullable=False) + platform_id = Column(String(50), nullable=False) + + # 调用类型 + task_type = Column(String(50), nullable=False) # script、polish、chat + + # Token 用量 + prompt_tokens = Column(Integer, default=0) + completion_tokens = Column(Integer, default=0) + total_tokens = Column(Integer, default=0) + + # 成本(计算后的人民币) + cost_cny = Column(Float, default=0.0) + + # 性能 + response_time_ms = Column(Integer, nullable=True) # 响应时间 + + # 结果 + success = Column(Boolean, default=True) + error_message = Column(Text, nullable=True) + + # 用户/项目 + user_id = Column(String(50), nullable=True) + project_id = Column(String(50), nullable=True) + + # 时间 + created_at = Column(DateTime, default=datetime.utcnow) + + # 索引定义 + __table_args__ = ( + # 索引:按用户查询使用记录 + Index("ix_model_usage_logs_user_id", "user_id"), + # 索引:按时间查询(用于统计) + Index("ix_model_usage_logs_created_at", "created_at"), + ) + + def __repr__(self): + return f"" diff --git a/python-api/app/models/user.py b/python-api/app/models/user.py new file mode 100644 index 0000000..07d5722 --- /dev/null +++ b/python-api/app/models/user.py @@ -0,0 +1,41 @@ +""" +用户模型 +======== + +采用"手机号 + JWT"的传统认证方案。 +""" + +from sqlalchemy import String, Text +from sqlalchemy.orm import Mapped, mapped_column + +from app.models.base import BaseModel + + +class User(BaseModel): + """用户表""" + + __tablename__ = "users" + + # 手机号,作为登录账号 + mobile: Mapped[str] = mapped_column( + String(20), + unique=True, + index=True, + nullable=False, + comment="手机号", + ) + + nickname: Mapped[str | None] = mapped_column( + String(64), + nullable=True, + comment="用户昵称", + ) + + avatar_url: Mapped[str | None] = mapped_column( + Text, + nullable=True, + comment="头像 URL", + ) + + def __repr__(self) -> str: + return f"" diff --git a/python-api/app/scheduler/__init__.py b/python-api/app/scheduler/__init__.py new file mode 100644 index 0000000..0fd8e19 --- /dev/null +++ b/python-api/app/scheduler/__init__.py @@ -0,0 +1,6 @@ +""" +统一异步任务调度器 +================== + +统一异步任务调度器(替代原 Celery 架构)。 +""" diff --git a/python-api/app/scheduler/engine.py b/python-api/app/scheduler/engine.py new file mode 100644 index 0000000..02e270c --- /dev/null +++ b/python-api/app/scheduler/engine.py @@ -0,0 +1,121 @@ +""" +Async Engine 核心调度器 +======================= + +驱动所有 Handler 的 Tick 循环,批量查询、批量更新。 +""" + +import asyncio +import logging +from typing import Any + +from app.core.redis_client import get_redis_client +from app.scheduler.handlers.base import AsyncHandler +from app.scheduler.models import StateChange +from app.scheduler.registry import JobRegistry +from app.scheduler.slot_manager import SlotManager + +logger = logging.getLogger(__name__) + + +class AsyncEngine: + """统一异步作业调度引擎""" + + def __init__(self, handlers: list[AsyncHandler] | None = None): + self.redis = get_redis_client() + self.registry = JobRegistry(self.redis) + self.slots = SlotManager(self.redis) + self.handlers: dict[str, AsyncHandler] = {} + if handlers: + for h in handlers: + self.handlers[h.name] = h + + def register(self, handler: AsyncHandler) -> None: + """注册一个 Handler""" + self.handlers[handler.name] = handler + logger.info(f"Registered handler: {handler.name}") + + async def tick(self) -> None: + """执行一次完整的调度 Tick""" + tick_start = asyncio.get_event_loop().time() + + try: + # 1. 加载所有 running 的作业 ID + running_ids = await self.registry.get_running_job_ids() + if not running_ids: + logger.debug("Tick: no running jobs") + return + + # 2. 按 job_type 分组 + jobs_by_type: dict[str, list[Any]] = {} + for job_id in running_ids: + record = await self.registry.get(job_id) + if not record: + await self.registry.remove_running(job_id) + continue + jobs_by_type.setdefault(record.job_type, []).append(record) + + # 3. 并行执行各 Handler 的 tick + results = await asyncio.gather( + *[ + self._safe_tick(handler_name, handler, jobs_by_type.get(handler_name, [])) + for handler_name, handler in self.handlers.items() + ] + ) + + # 4. 收集并应用状态变更 + for changes in results: + if changes: + await self._apply_changes(changes) + + # 5. 清理已结束的作业 + await self._cleanup_finished() + + except Exception: + logger.exception("Scheduler tick failed") + finally: + elapsed = asyncio.get_event_loop().time() - tick_start + logger.debug(f"Tick completed in {elapsed:.2f}s") + + async def _safe_tick( + self, name: str, handler: AsyncHandler, jobs: list[Any] + ) -> list[StateChange]: + """安全执行 Handler tick,捕获异常""" + try: + return await handler.tick(jobs, self.registry, self.slots) + except Exception: + logger.exception(f"Handler tick failed: {name}") + return [] + + async def _apply_changes(self, changes: list[StateChange]) -> None: + """批量应用状态变更到 Redis""" + pipe = self.redis.pipeline() + executed = False + for change in changes: + key, field, value = change.to_redis_command() + pipe.hset(key, field, value) + executed = True + if executed: + await pipe.execute() + + async def _cleanup_finished(self) -> None: + """清理已完成的作业""" + running_ids = await self.registry.get_running_job_ids() + for job_id in running_ids: + record = await self.registry.get(job_id) + if not record: + await self.registry.remove_running(job_id) + continue + if record.status in ("completed", "failed"): + await self.registry.remove_running(job_id) + logger.info(f"Job moved to finished: {job_id} ({record.status})") + + async def run_forever(self, interval: float = 10.0, min_interval: float = 2.0) -> None: + """启动无限 Tick 循环""" + logger.info("Async Engine started") + while True: + tick_start = asyncio.get_event_loop().time() + await self.tick() + elapsed = asyncio.get_event_loop().time() - tick_start + sleep_time = max(interval - elapsed, min_interval) + await asyncio.sleep(sleep_time) diff --git a/python-api/app/scheduler/handlers/__init__.py b/python-api/app/scheduler/handlers/__init__.py new file mode 100644 index 0000000..358d614 --- /dev/null +++ b/python-api/app/scheduler/handlers/__init__.py @@ -0,0 +1,3 @@ +""" +Scheduler Handlers +""" diff --git a/python-api/app/scheduler/handlers/avatar_handler.py b/python-api/app/scheduler/handlers/avatar_handler.py new file mode 100644 index 0000000..d68e0f6 --- /dev/null +++ b/python-api/app/scheduler/handlers/avatar_handler.py @@ -0,0 +1,504 @@ +""" +Avatar 形象克隆处理器 +==================== + +管理 Kling 形象克隆的提交与轮询。 +占用全局槽位:2 + +数据策略:不操作数据库,所有中间状态存储在 Redis 中。 +""" + +import asyncio +import contextlib +import json +import logging +from datetime import UTC, datetime +from typing import Any + +import aiohttp + +from app.ai.providers.klingai_provider import KlingAIProvider +from app.config import get_settings +from app.core.redis_client import get_redis_client +from app.scheduler.handlers.base import AsyncHandler +from app.scheduler.models import StateChange +from app.scheduler.registry import JobRegistry +from app.scheduler.slot_manager import SlotManager +from app.schemas.enums import AvatarCloneStatus + +logger = logging.getLogger(__name__) + +SLOT_KEY = "kling:avatar_slots" +MAX_SLOTS = 2 + +SYSTEM_BUSY_MESSAGE = "系统繁忙,请稍后重试" +SYSTEM_ERROR_MESSAGE = "系统处理异常,请稍后重试或联系客服" + + +def _get_kling_provider() -> KlingAIProvider: + settings = get_settings() + return KlingAIProvider( + config={ + "access_key": settings.KLINGAI_ACCESS_KEY or "", + "secret_key": settings.KLINGAI_SECRET_KEY or "", + } + ) + + +def _translate_voice_error(message: str) -> str: + msg = (message or "").lower() + if "no valid audio" in msg or "audio" in msg or "voice" in msg or "人声" in msg: + return "自定义音色创建失败:视频中没有检测到清晰的人声。请确保上传「有声的人物视频」,且人声干净、无杂音、背景噪音小。" + if "duration" in msg or "时长" in msg: + return "自定义音色创建失败:视频时长不符合要求。请使用 5-30 秒的视频。" + if "format" in msg or "格式" in msg: + return "自定义音色创建失败:视频格式不支持。请使用 MP4 或 MOV 格式。" + if "size" in msg or "大小" in msg or "mb" in msg: + return "自定义音色创建失败:视频文件过大。请压缩至 200MB 以内。" + if "quality" in msg or "质量" in msg: + return "自定义音色创建失败:视频/音频质量不符合要求。请确保画面清晰、人声干净、无强烈背景噪音。" + return f"自定义音色创建失败:{message}。请检查是否上传了符合要求的「有声的人物视频」。" + + +def _translate_element_error(message: str) -> str: + msg = (message or "").lower() + if "duration" in msg or "时长" in msg: + return "主体创建失败:视频时长不符合要求。请使用 3-8 秒的人物特写视频。" + if "resolution" in msg or "height" in msg or "像素" in msg or "720" in msg or "2160" in msg: + return "主体创建失败:视频分辨率不符合要求。请确保视频高度在 720px~2160px 之间。" + if "size" in msg or "大小" in msg or "mb" in msg or "200" in msg: + return "主体创建失败:视频文件过大。请压缩至 200MB 以内。" + if "format" in msg or "格式" in msg or "mp4" in msg or "mov" in msg: + return "主体创建失败:视频格式不支持。请使用 MP4 或 MOV 格式。" + if "face" in msg or "人脸" in msg or "detect" in msg or "主体" in msg: + return "主体创建失败:未能从视频中检测到稳定的人脸。请确保视频为「写实风格的人物正面特写」,人脸清晰、无遮挡、光线充足。" + if "human" in msg or "人形" in msg or "character" in msg or "写实" in msg: + return "主体创建失败:视频内容不符合要求。请确保视频中是「写实风格的真实人物」,非卡通、非动物、非虚拟形象。" + return f"主体创建失败:{message}。请检查视频是否为 3-8 秒、人脸清晰、写实风格的正面人物视频。" + + +def _translate_system_error(error: Exception, step: str) -> tuple[str, str]: + error_str = str(error) + error_type = type(error).__name__ + if isinstance(error, aiohttp.ClientError | asyncio.TimeoutError): + return SYSTEM_BUSY_MESSAGE, f"[{step}] 网络错误: {error_type}: {error_str}" + if "500" in error_str or "503" in error_str or "502" in error_str: + return SYSTEM_BUSY_MESSAGE, f"[{step}] KlingAI 服务错误: {error_type}: {error_str}" + if ( + "rate limit" in error_str.lower() + or "too many requests" in error_str.lower() + or "429" in error_str + ): + return SYSTEM_BUSY_MESSAGE, f"[{step}] API 限流: {error_type}: {error_str}" + return SYSTEM_ERROR_MESSAGE, f"[{step}] 系统错误: {error_type}: {error_str}" + + +async def _update_avatar_state(registry: JobRegistry, avatar_id: str, **fields: Any) -> None: + """更新 Redis 中的 avatar 状态(同时更新 updated_at)""" + fields["updated_at"] = datetime.now(UTC).isoformat() + await registry.update(avatar_id, **fields) + + +class AvatarHandler(AsyncHandler): + name = "avatar_clone" + slot_key = SLOT_KEY + max_slots = MAX_SLOTS + + async def tick( + self, jobs: list[Any], registry: JobRegistry, slots: SlotManager + ) -> list[StateChange]: + changes: list[StateChange] = [] + for job in jobs: + job_changes = await self._process_job(job, registry, slots) + changes.extend(job_changes) + return changes + + async def _process_job( + self, job: Any, registry: JobRegistry, slots: SlotManager + ) -> list[StateChange]: + changes: list[StateChange] = [] + avatar_id = job.job_id + + # 从 Redis 读取 avatar 状态 + redis = get_redis_client() + state_raw = await redis.hgetall(f"job:{avatar_id}") + if not state_raw: + logger.error(f"Avatar job not found in Redis: {avatar_id}") + _msg = "任务记录丢失,请重新提交" + changes.append(StateChange(job_id=avatar_id, field_path="status", value="failed")) + changes.append(StateChange(job_id=avatar_id, field_path="message", value=_msg)) + changes.append(StateChange(job_id=avatar_id, field_path="error", value=_msg)) + return changes + + # 解析 params + params = {} + if "params" in state_raw and state_raw["params"]: + with contextlib.suppress(json.JSONDecodeError): + params = json.loads(state_raw["params"]) + + status = state_raw.get("avatar_status", state_raw.get("status", "")) + provider = _get_kling_provider() + + # 辅助函数:读取字段 + def _f(key: str) -> str: + return state_raw.get(key, "") or "" + + # ---------- pending: 创建音色 ---------- + if status == AvatarCloneStatus.PENDING.value: + slot_id = f"avatar:{avatar_id}" + acquired = await slots.acquire(SLOT_KEY, slot_id, MAX_SLOTS) + if not acquired: + return changes # 槽位已满,等下一轮 + + try: + await _update_avatar_state( + registry, avatar_id, avatar_status=AvatarCloneStatus.VOICE_PROCESSING.value + ) + changes.append( + StateChange( + job_id=avatar_id, field_path="message", value="正在创建自定义音色..." + ) + ) + voice_result = await provider.create_custom_voice( + voice_name=params.get("name", ""), + video_url=params.get("video_url", ""), + ) + voice_task_id = voice_result.get("task_id") + if not voice_task_id: + raise Exception("未返回音色任务 ID") + await _update_avatar_state(registry, avatar_id, provider_voice_job_id=voice_task_id) + logger.info(f"Avatar {avatar_id}: created voice task {voice_task_id}") + except Exception as e: + await slots.release(SLOT_KEY, slot_id) + if isinstance(e, aiohttp.ClientError | asyncio.TimeoutError) or any( + code in str(e) for code in ["500", "503", "502", "429"] + ): + user_msg, cloud_detail = _translate_system_error(e, "voice_create") + await _update_avatar_state( + registry, + avatar_id, + avatar_status=AvatarCloneStatus.VOICE_FAILED.value, + fail_reason=user_msg, + ) + logger.error(f"Avatar {avatar_id} voice_create system error: {cloud_detail}") + changes.append( + StateChange(job_id=avatar_id, field_path="status", value="failed") + ) + changes.append( + StateChange(job_id=avatar_id, field_path="message", value=user_msg) + ) + changes.append( + StateChange(job_id=avatar_id, field_path="error", value=user_msg) + ) + else: + _reason = _translate_voice_error(str(e)) + await _update_avatar_state( + registry, + avatar_id, + avatar_status=AvatarCloneStatus.VOICE_FAILED.value, + fail_reason=_reason, + ) + changes.append( + StateChange(job_id=avatar_id, field_path="status", value="failed") + ) + changes.append( + StateChange(job_id=avatar_id, field_path="message", value=_reason) + ) + changes.append(StateChange(job_id=avatar_id, field_path="error", value=_reason)) + + # ---------- voice_processing: 轮询音色 ---------- + elif status == AvatarCloneStatus.VOICE_PROCESSING.value: + provider_voice_job_id = _f("provider_voice_job_id") + if not provider_voice_job_id: + return changes + try: + result = await provider.get_custom_voice_task(provider_voice_job_id) + kling_status = result.get("task_status", "processing") + logger.info( + f"Avatar {avatar_id}: voice task {provider_voice_job_id} status={kling_status}" + ) + if kling_status == "processing": + changes.append( + StateChange(job_id=avatar_id, field_path="message", value="音色处理中...") + ) + elif kling_status == "succeed": + await slots.release(SLOT_KEY, f"avatar:{avatar_id}") + task_result = result.get("task_result", {}) + voices = task_result.get("voices", []) + voice_id = None + trial_url = None + if voices: + voice_info = voices[0] + voice_id = voice_info.get("voice_id") or voice_info.get("id") + trial_url = ( + voice_info.get("trial_url") + or voice_info.get("preview_url") + or voice_info.get("voice_url") + ) + if not voice_id: + raise Exception("音色任务成功但未返回 voice_id") + await _update_avatar_state( + registry, + avatar_id, + avatar_status=AvatarCloneStatus.ELEMENT_PENDING.value, + voice_id=voice_id, + trial_url=trial_url or "", + ) + changes.append( + StateChange( + job_id=avatar_id, + field_path="message", + value="音色创建成功,准备创建形象主体...", + ) + ) + logger.info(f"Avatar {avatar_id}: voice succeed, voice_id={voice_id}") + + elif kling_status == "failed": + await slots.release(SLOT_KEY, f"avatar:{avatar_id}") + error_msg = result.get("task_msg", "任务执行失败") + _reason = _translate_voice_error(error_msg) + await _update_avatar_state( + registry, + avatar_id, + avatar_status=AvatarCloneStatus.VOICE_FAILED.value, + fail_reason=_reason, + ) + changes.append( + StateChange(job_id=avatar_id, field_path="status", value="failed") + ) + changes.append( + StateChange(job_id=avatar_id, field_path="message", value=_reason) + ) + changes.append(StateChange(job_id=avatar_id, field_path="error", value=_reason)) + except Exception as e: + logger.exception(f"Avatar {avatar_id}: voice poll error") + if isinstance(e, aiohttp.ClientError | asyncio.TimeoutError) or any( + code in str(e) for code in ["500", "503", "502", "429"] + ): + user_msg, cloud_detail = _translate_system_error(e, "voice_poll") + await _update_avatar_state( + registry, + avatar_id, + avatar_status=AvatarCloneStatus.VOICE_FAILED.value, + fail_reason=user_msg, + ) + logger.error(f"Avatar {avatar_id} voice_poll system error: {cloud_detail}") + changes.append( + StateChange(job_id=avatar_id, field_path="status", value="failed") + ) + changes.append( + StateChange(job_id=avatar_id, field_path="message", value=user_msg) + ) + changes.append( + StateChange(job_id=avatar_id, field_path="error", value=user_msg) + ) + else: + _reason = _translate_voice_error(str(e)) + await _update_avatar_state( + registry, + avatar_id, + avatar_status=AvatarCloneStatus.VOICE_FAILED.value, + fail_reason=_reason, + ) + changes.append( + StateChange(job_id=avatar_id, field_path="status", value="failed") + ) + changes.append( + StateChange(job_id=avatar_id, field_path="message", value=_reason) + ) + changes.append(StateChange(job_id=avatar_id, field_path="error", value=_reason)) + + # ---------- element_pending: 创建主体 ---------- + elif status == AvatarCloneStatus.ELEMENT_PENDING.value: + slot_id = f"avatar:{avatar_id}" + acquired = await slots.acquire(SLOT_KEY, slot_id, MAX_SLOTS) + if not acquired: + return changes + + try: + await _update_avatar_state( + registry, avatar_id, avatar_status=AvatarCloneStatus.ELEMENT_PROCESSING.value + ) + changes.append( + StateChange(job_id=avatar_id, field_path="message", value="正在创建形象主体...") + ) + element_result = await provider.create_element( + element_name=params.get("name", ""), + element_description=f"{params.get('name', '')} 的克隆形象", + reference_type="video_refer", + element_video_list={ + "refer_videos": [{"video_url": params.get("video_url", "")}] + }, + element_voice_id=_f("voice_id"), + ) + element_task_id = element_result.get("task_id") + if not element_task_id: + raise Exception("未返回主体任务 ID") + await _update_avatar_state( + registry, avatar_id, provider_element_job_id=element_task_id + ) + logger.info(f"Avatar {avatar_id}: created element task {element_task_id}") + except Exception as e: + await slots.release(SLOT_KEY, slot_id) + if isinstance(e, aiohttp.ClientError | asyncio.TimeoutError) or any( + code in str(e) for code in ["500", "503", "502", "429"] + ): + user_msg, cloud_detail = _translate_system_error(e, "element_create") + await _update_avatar_state( + registry, + avatar_id, + avatar_status=AvatarCloneStatus.ELEMENT_FAILED.value, + fail_reason=user_msg, + ) + logger.error(f"Avatar {avatar_id} element_create system error: {cloud_detail}") + changes.append( + StateChange(job_id=avatar_id, field_path="status", value="failed") + ) + changes.append( + StateChange(job_id=avatar_id, field_path="message", value=user_msg) + ) + changes.append( + StateChange(job_id=avatar_id, field_path="error", value=user_msg) + ) + else: + _reason = _translate_element_error(str(e)) + await _update_avatar_state( + registry, + avatar_id, + avatar_status=AvatarCloneStatus.ELEMENT_FAILED.value, + fail_reason=_reason, + ) + changes.append( + StateChange(job_id=avatar_id, field_path="status", value="failed") + ) + changes.append( + StateChange(job_id=avatar_id, field_path="message", value=_reason) + ) + changes.append(StateChange(job_id=avatar_id, field_path="error", value=_reason)) + + # ---------- element_processing: 轮询主体 ---------- + elif status == AvatarCloneStatus.ELEMENT_PROCESSING.value: + provider_element_job_id = _f("provider_element_job_id") + if not provider_element_job_id: + return changes + try: + result = await provider.get_element_task(provider_element_job_id) + kling_status = result.get("task_status", "processing") + logger.info( + f"Avatar {avatar_id}: element task {provider_element_job_id} status={kling_status}" + ) + if kling_status == "processing": + changes.append( + StateChange( + job_id=avatar_id, field_path="message", value="形象主体处理中..." + ) + ) + elif kling_status == "succeed": + await slots.release(SLOT_KEY, f"avatar:{avatar_id}") + task_result = result.get("task_result", {}) + elements = task_result.get("elements", []) + element_id = None + if elements: + element_id = elements[0].get("element_id") + if not element_id: + element_id = task_result.get("element_id") + if not element_id: + raise Exception("主体任务成功但未返回 element_id") + await _update_avatar_state( + registry, + avatar_id, + avatar_status=AvatarCloneStatus.SUCCEED.value, + provider_element_id=str(element_id), + ) + changes.append( + StateChange(job_id=avatar_id, field_path="status", value="completed") + ) + changes.append( + StateChange( + job_id=avatar_id, + field_path="result", + value={ + "avatar_id": avatar_id, + "name": params.get("name", ""), + "video_url": params.get("video_url", ""), + "voice_id": _f("voice_id"), + "element_id": int(element_id), + "trial_url": _f("trial_url"), + }, + ) + ) + logger.info(f"Avatar {avatar_id}: element succeed, element_id={element_id}") + + elif kling_status == "failed": + await slots.release(SLOT_KEY, f"avatar:{avatar_id}") + error_msg = result.get("task_msg", "任务执行失败") + _reason = _translate_element_error(error_msg) + await _update_avatar_state( + registry, + avatar_id, + avatar_status=AvatarCloneStatus.ELEMENT_FAILED.value, + fail_reason=_reason, + ) + changes.append( + StateChange(job_id=avatar_id, field_path="status", value="failed") + ) + changes.append( + StateChange(job_id=avatar_id, field_path="message", value=_reason) + ) + changes.append(StateChange(job_id=avatar_id, field_path="error", value=_reason)) + except Exception as e: + logger.exception(f"Avatar {avatar_id}: element poll error") + if isinstance(e, aiohttp.ClientError | asyncio.TimeoutError) or any( + code in str(e) for code in ["500", "503", "502", "429"] + ): + user_msg, cloud_detail = _translate_system_error(e, "element_poll") + await _update_avatar_state( + registry, + avatar_id, + avatar_status=AvatarCloneStatus.ELEMENT_FAILED.value, + fail_reason=user_msg, + ) + logger.error(f"Avatar {avatar_id} element_poll system error: {cloud_detail}") + changes.append( + StateChange(job_id=avatar_id, field_path="status", value="failed") + ) + changes.append( + StateChange(job_id=avatar_id, field_path="message", value=user_msg) + ) + changes.append( + StateChange(job_id=avatar_id, field_path="error", value=user_msg) + ) + else: + _reason = _translate_element_error(str(e)) + await _update_avatar_state( + registry, + avatar_id, + avatar_status=AvatarCloneStatus.ELEMENT_FAILED.value, + fail_reason=_reason, + ) + changes.append( + StateChange(job_id=avatar_id, field_path="status", value="failed") + ) + changes.append( + StateChange(job_id=avatar_id, field_path="message", value=_reason) + ) + changes.append(StateChange(job_id=avatar_id, field_path="error", value=_reason)) + + # ---------- 已结束状态:移出 running ---------- + elif status in ( + AvatarCloneStatus.SUCCEED.value, + AvatarCloneStatus.VOICE_FAILED.value, + AvatarCloneStatus.ELEMENT_FAILED.value, + ): + await slots.release(SLOT_KEY, f"avatar:{avatar_id}") + if status == AvatarCloneStatus.SUCCEED.value: + changes.append( + StateChange(job_id=avatar_id, field_path="status", value="completed") + ) + else: + _msg = "任务状态异常" + changes.append(StateChange(job_id=avatar_id, field_path="status", value="failed")) + changes.append(StateChange(job_id=avatar_id, field_path="message", value=_msg)) + changes.append(StateChange(job_id=avatar_id, field_path="error", value=_msg)) + + return changes diff --git a/python-api/app/scheduler/handlers/base.py b/python-api/app/scheduler/handlers/base.py new file mode 100644 index 0000000..8529512 --- /dev/null +++ b/python-api/app/scheduler/handlers/base.py @@ -0,0 +1,38 @@ +""" +AsyncHandler 抽象基类 +""" + +from abc import ABC, abstractmethod +from typing import Any + +from app.scheduler.models import StateChange +from app.scheduler.registry import JobRegistry +from app.scheduler.slot_manager import SlotManager + + +class AsyncHandler(ABC): + """第三方异步任务处理器基类""" + + name: str + slot_key: str + max_slots: int + + @abstractmethod + async def tick( + self, + jobs: list[Any], + registry: JobRegistry, + slots: SlotManager, + ) -> list[StateChange]: + """ + 每个 Tick 执行一次。 + + Args: + jobs: 当前 running 状态的作业记录列表 + registry: 作业注册表(用于读写 Redis 状态) + slots: 全局槽位管理器 + + Returns: + 状态变更列表 + """ + pass diff --git a/python-api/app/scheduler/handlers/copy_handler.py b/python-api/app/scheduler/handlers/copy_handler.py new file mode 100644 index 0000000..20971d2 --- /dev/null +++ b/python-api/app/scheduler/handlers/copy_handler.py @@ -0,0 +1,120 @@ +""" +Copy 任务处理器 +============== + +管理 AnyToCopy 文案提取的提交与轮询。 +""" + +import logging +from typing import Any + +from app.scheduler.handlers.base import AsyncHandler +from app.scheduler.models import StateChange +from app.scheduler.registry import JobRegistry +from app.scheduler.slot_manager import SlotManager +from app.services.anytocopy_service import get_anytocopy_service + +logger = logging.getLogger(__name__) + +SLOT_KEY = "anytocopy:slots" +MAX_SLOTS = 5 + + +class CopyHandler(AsyncHandler): + name = "copy" + slot_key = SLOT_KEY + max_slots = MAX_SLOTS + + async def tick( + self, jobs: list[Any], registry: JobRegistry, slots: SlotManager + ) -> list[StateChange]: + changes: list[StateChange] = [] + + for job in jobs: + params = job.params or {} + anytocopy_task_id = params.get("anytocopy_task_id") + video_url = params.get("url", params.get("video_url", "")) + + if anytocopy_task_id: + try: + service = get_anytocopy_service() + result = await service.query_task(anytocopy_task_id) + if result.get("code") != 200: + continue + data = result.get("data", {}) + status = data.get("status") + + if status == "SUCCESS": + result_data = { + "video_url": video_url, + "title": data.get("title", ""), + "content": data.get("content", ""), + "text_content": data.get("textContent", ""), + "platform": data.get("platform", ""), + "duration": data.get("duration", 0), + } + await slots.release(SLOT_KEY, job.job_id) + changes.append( + StateChange(job_id=job.job_id, field_path="status", value="completed") + ) + changes.append( + StateChange( + job_id=job.job_id, field_path="message", value="文案提取完成" + ) + ) + changes.append( + StateChange(job_id=job.job_id, field_path="completed", value=1) + ) + changes.append(StateChange(job_id=job.job_id, field_path="total", value=1)) + changes.append( + StateChange(job_id=job.job_id, field_path="result", value=result_data) + ) + elif status in ("FAILED", "FAILURE"): + await slots.release(SLOT_KEY, job.job_id) + changes.append( + StateChange(job_id=job.job_id, field_path="status", value="failed") + ) + changes.append( + StateChange( + job_id=job.job_id, + field_path="message", + value=f"提取失败: {data.get('errorMessage', '未知错误')}", + ) + ) + changes.append( + StateChange( + job_id=job.job_id, + field_path="error", + value=data.get("errorMessage", ""), + ) + ) + except Exception as e: + logger.error(f"[Copy {job.job_id}] poll error: {e}") + continue + + acquired = await slots.acquire(SLOT_KEY, job.job_id, MAX_SLOTS) + if not acquired: + continue + + try: + service = get_anytocopy_service() + submit_result = await service.submit_task(video_url) + if submit_result.get("code") != 200: + raise Exception(f"提交失败: {submit_result.get('msg')}") + anytocopy_task_id = submit_result["data"] + params["anytocopy_task_id"] = anytocopy_task_id + changes.append(StateChange(job_id=job.job_id, field_path="params", value=params)) + changes.append( + StateChange(job_id=job.job_id, field_path="message", value="文案提取任务已提交") + ) + except Exception as e: + await slots.release(SLOT_KEY, job.job_id) + changes.append(StateChange(job_id=job.job_id, field_path="status", value="failed")) + changes.append( + StateChange(job_id=job.job_id, field_path="message", value=str(e)[:200]) + ) + changes.append( + StateChange(job_id=job.job_id, field_path="error", value=str(e)[:500]) + ) + + return changes diff --git a/python-api/app/scheduler/handlers/image_handler.py b/python-api/app/scheduler/handlers/image_handler.py new file mode 100644 index 0000000..91d5c39 --- /dev/null +++ b/python-api/app/scheduler/handlers/image_handler.py @@ -0,0 +1,190 @@ +""" +Image 任务处理器 +=============== + +管理 Kling 图片生成的提交、轮询、下载。 +不占用 Kling Video/Avatar 槽位,使用独立的图片槽位池。 +""" + +import logging +from pathlib import Path +from typing import Any + +import aiohttp + +from app.ai.providers.klingai_provider import KlingAIProvider +from app.config import get_settings +from app.core.config_loader import get_config_loader +from app.scheduler.handlers.base import AsyncHandler +from app.scheduler.models import StateChange +from app.scheduler.registry import JobRegistry +from app.scheduler.slot_manager import SlotManager + +logger = logging.getLogger(__name__) + +SLOT_KEY = "kling:image_slots" +MAX_SLOTS = 9 + + +class ImageHandler(AsyncHandler): + name = "image" + slot_key = SLOT_KEY + max_slots = MAX_SLOTS + + async def _get_provider(self) -> KlingAIProvider: + settings = get_settings() + config_loader = get_config_loader() + platform = config_loader.get_platform("klingai") + return KlingAIProvider( + { + "access_key": settings.KLINGAI_ACCESS_KEY or "", + "secret_key": settings.KLINGAI_SECRET_KEY or "", + "base_url": platform.base_url if platform else "https://api-beijing.klingai.com", + } + ) + + async def tick( + self, jobs: list[Any], registry: JobRegistry, slots: SlotManager + ) -> list[StateChange]: + changes: list[StateChange] = [] + provider = await self._get_provider() + + for job in jobs: + params = job.params or {} + provider_task_id = params.get("provider_task_id") + project_id = params.get("project_id", "") + prompt = params.get("prompt", "") + image_type = params.get("image_type", "cover") + + if provider_task_id: + # 轮询状态 + try: + result = await provider.get_image_task(provider_task_id) + status = result.get("task_status", "unknown") + except Exception as e: + logger.error(f"[Image {job.job_id}] poll error: {e}") + continue + + if status in ("processing", "submitted"): + continue + + if status == "failed": + await slots.release(SLOT_KEY, job.job_id) + error_msg = result.get("task_status_msg", "图片生成失败") + changes.append( + StateChange(job_id=job.job_id, field_path="status", value="failed") + ) + changes.append( + StateChange(job_id=job.job_id, field_path="message", value=error_msg) + ) + changes.append( + StateChange(job_id=job.job_id, field_path="error", value=error_msg) + ) + continue + + # succeed + images = result.get("task_result", {}).get("images", []) + if not images: + await slots.release(SLOT_KEY, job.job_id) + changes.append( + StateChange(job_id=job.job_id, field_path="status", value="failed") + ) + changes.append( + StateChange( + job_id=job.job_id, + field_path="message", + value="图片生成成功但未返回图片", + ) + ) + continue + + image_url = images[0].get("url") + image_dir = ( + Path.home() / "Documents" / "Meijiaka" / "projects" / project_id / "images" + ) + image_dir.mkdir(parents=True, exist_ok=True) + ext = ".jpg" if ".jpg" in image_url else ".png" + local_path = image_dir / f"{image_type}_{job.job_id[:8]}{ext}" + + try: + async with aiohttp.ClientSession() as session, session.get(image_url) as resp: + resp.raise_for_status() + local_path.write_bytes(await resp.read()) + except Exception as e: + await slots.release(SLOT_KEY, job.job_id) + changes.append( + StateChange(job_id=job.job_id, field_path="status", value="failed") + ) + changes.append( + StateChange( + job_id=job.job_id, field_path="message", value=f"图片下载失败: {e}" + ) + ) + continue + + await slots.release(SLOT_KEY, job.job_id) + result_data = { + "project_id": project_id, + "image_type": image_type, + "local_path": str(local_path), + "prompt": prompt, + } + changes.append( + StateChange(job_id=job.job_id, field_path="status", value="completed") + ) + changes.append( + StateChange(job_id=job.job_id, field_path="message", value="图片生成完成") + ) + changes.append(StateChange(job_id=job.job_id, field_path="completed", value=1)) + changes.append(StateChange(job_id=job.job_id, field_path="total", value=1)) + changes.append( + StateChange(job_id=job.job_id, field_path="result", value=result_data) + ) + continue + + # 提交新任务 + acquired = await slots.acquire(SLOT_KEY, job.job_id, MAX_SLOTS) + if not acquired: + continue + + try: + reference_image = params.get("reference_image") + human_id = params.get("human_id") + if reference_image: + result = await provider.generate_image( + prompt=prompt, + image_url=reference_image, + model="kling-v3", + ) + elif human_id: + result = await provider.generate_image( + prompt=prompt, + model="kling-v3", + aspect_ratio="9:16", + ) + else: + result = await provider.generate_image( + prompt=prompt, + model="kling-v3", + aspect_ratio="9:16", + ) + + provider_task_id = result.get("task_id") + if not provider_task_id: + raise ValueError("未返回任务ID") + params["provider_task_id"] = provider_task_id + changes.append(StateChange(job_id=job.job_id, field_path="params", value=params)) + changes.append( + StateChange(job_id=job.job_id, field_path="message", value="图片任务已提交") + ) + except Exception as e: + await slots.release(SLOT_KEY, job.job_id) + changes.append(StateChange(job_id=job.job_id, field_path="status", value="failed")) + changes.append( + StateChange(job_id=job.job_id, field_path="message", value=str(e)[:200]) + ) + changes.append( + StateChange(job_id=job.job_id, field_path="error", value=str(e)[:500]) + ) + + return changes diff --git a/python-api/app/scheduler/handlers/script_handler.py b/python-api/app/scheduler/handlers/script_handler.py new file mode 100644 index 0000000..4e55e31 --- /dev/null +++ b/python-api/app/scheduler/handlers/script_handler.py @@ -0,0 +1,141 @@ +""" +Script 任务处理器 +================ + +管理脚本生成的执行。 +不占用 Kling/Volc 槽位,使用独立的 script 槽位池。 +""" + +import logging +from typing import Any + +from app.scheduler.handlers.base import AsyncHandler +from app.scheduler.models import StateChange +from app.scheduler.registry import JobRegistry +from app.scheduler.slot_manager import SlotManager +from app.services.anytocopy_service import get_anytocopy_service +from app.services.script_service import ScriptService + +logger = logging.getLogger(__name__) + +SLOT_KEY = "script:slots" +MAX_SLOTS = 10 + + +class ScriptHandler(AsyncHandler): + name = "script" + slot_key = SLOT_KEY + max_slots = MAX_SLOTS + + async def tick( + self, jobs: list[Any], registry: JobRegistry, slots: SlotManager + ) -> list[StateChange]: + changes: list[StateChange] = [] + + for job in jobs: + acquired = await slots.acquire(SLOT_KEY, job.job_id, MAX_SLOTS) + if not acquired: + continue + + try: + changes.extend(await self._process_job(job, registry, slots)) + except Exception as e: + logger.exception(f"[Script {job.job_id}] failed") + changes.append(StateChange(job_id=job.job_id, field_path="status", value="failed")) + changes.append( + StateChange(job_id=job.job_id, field_path="error", value=str(e)[:500]) + ) + finally: + await slots.release(SLOT_KEY, job.job_id) + + return changes + + async def _process_job( + self, job: Any, registry: JobRegistry, slots: SlotManager + ) -> list[StateChange]: + changes: list[StateChange] = [] + params = job.params or {} + topic = params.get("topic", "") + style = params.get("style", "default") + duration = params.get("duration", 60) + + await registry.update( + job.job_id, + status="running", + progress=10, + message="分析需求中...", + completed=0, + total=1, + ) + + try: + await __import__("asyncio").sleep(2) + anytocopy = get_anytocopy_service() + extract_result = await anytocopy.extract_text_from_input(topic) + extracted_info = None + actual_topic = topic + is_video_url = extract_result.get("is_video_url", False) + + if is_video_url: + await registry.update( + job.job_id, + progress=30, + message="提取视频素材中...", + ) + video_info = extract_result.get("video_info") + if video_info: + extracted_info = { + "title": video_info.title, + "content": video_info.content, + "text_content": video_info.text_content, + "platform": video_info.platform, + "duration": video_info.duration, + "original_url": topic, + } + actual_topic = extract_result.get("extracted_text") or topic + await registry.update( + job.job_id, + progress=60, + message="生成脚本中...", + ) + else: + await registry.update( + job.job_id, + progress=40, + message="构思脚本中...", + ) + + service = ScriptService() + shots = await service.generate_script( + topic=actual_topic, script_type=style, duration=duration + ) + + # 计算分镜真实总时长 + total_duration = sum(s.duration for s in shots if s.duration) + result_data = { + "title": actual_topic[:50], + "scenes": [s.model_dump() for s in shots], + "total_duration": total_duration, + "style": style, + "shot_count": len(shots), + "extracted_info": extracted_info, + } + + changes.append(StateChange(job_id=job.job_id, field_path="status", value="completed")) + changes.append(StateChange(job_id=job.job_id, field_path="progress", value=100)) + changes.append( + StateChange(job_id=job.job_id, field_path="message", value="脚本生成完成") + ) + changes.append(StateChange(job_id=job.job_id, field_path="completed", value=1)) + changes.append(StateChange(job_id=job.job_id, field_path="total", value=1)) + changes.append(StateChange(job_id=job.job_id, field_path="result", value=result_data)) + + except Exception as exc: + logger.exception(f"[ScriptTask {job.job_id}] Failed") + changes.append(StateChange(job_id=job.job_id, field_path="status", value="failed")) + changes.append( + StateChange(job_id=job.job_id, field_path="message", value=str(exc)[:200]) + ) + changes.append(StateChange(job_id=job.job_id, field_path="error", value=str(exc)[:500])) + + return changes diff --git a/python-api/app/scheduler/handlers/subtitle_handler.py b/python-api/app/scheduler/handlers/subtitle_handler.py new file mode 100644 index 0000000..204208d --- /dev/null +++ b/python-api/app/scheduler/handlers/subtitle_handler.py @@ -0,0 +1,141 @@ +""" +Subtitle 任务处理器 +================== + +管理火山引擎字幕生成与自动打轴的提交与轮询。 +支持两种模式: +- caption: 字幕识别(从音频/视频提取带时间轴的字幕) +- auto_align: 自动打轴(为已有字幕文本配上时间轴) +""" + +import logging +from typing import Any + +from app.scheduler.handlers.base import AsyncHandler +from app.scheduler.models import StateChange +from app.scheduler.registry import JobRegistry +from app.scheduler.slot_manager import SlotManager +from app.services.volcengine_caption_service import VolcengineCaptionService + +logger = logging.getLogger(__name__) + +SLOT_KEY = "volc:subtitle_slots" +MAX_SLOTS = 5 + + +class SubtitleHandler(AsyncHandler): + name = "subtitle" + slot_key = SLOT_KEY + max_slots = MAX_SLOTS + + async def tick( + self, jobs: list[Any], registry: JobRegistry, slots: SlotManager + ) -> list[StateChange]: + changes: list[StateChange] = [] + + for job in jobs: + params = job.params or {} + mode = params.get("mode", "caption") + volc_task_id = params.get("volc_task_id") + project_id = params.get("project_id", "") + video_path = params.get("video", params.get("video_path", "")) + language = params.get("language", "zh") + audio_text = params.get("audio_text", "") + + if volc_task_id: + # 轮询 + try: + service = VolcengineCaptionService() + if mode == "auto_align": + result = await service.query_auto_align_task(volc_task_id, blocking=False) + else: + result = await service.query_caption_task(volc_task_id, blocking=False) + + if result.code == 0: + utterances = result.utterances or [] + result_data = { + "project_id": project_id, + "video_path": video_path, + "language": language, + "mode": mode, + "duration": result.duration, + "utterances": [ + { + "text": u.text, + "start_time": u.start_time, + "end_time": u.end_time, + } + for u in utterances + ], + } + await slots.release(SLOT_KEY, job.job_id) + changes.append( + StateChange(job_id=job.job_id, field_path="status", value="completed") + ) + changes.append( + StateChange( + job_id=job.job_id, field_path="message", value="字幕生成完成" + ) + ) + changes.append( + StateChange(job_id=job.job_id, field_path="completed", value=1) + ) + changes.append(StateChange(job_id=job.job_id, field_path="total", value=1)) + changes.append( + StateChange(job_id=job.job_id, field_path="result", value=result_data) + ) + elif result.code != 2000: + await slots.release(SLOT_KEY, job.job_id) + changes.append( + StateChange(job_id=job.job_id, field_path="status", value="failed") + ) + changes.append( + StateChange( + job_id=job.job_id, + field_path="message", + value=f"字幕识别失败: {result.message}", + ) + ) + changes.append( + StateChange(job_id=job.job_id, field_path="error", value=result.message) + ) + except Exception as e: + logger.error(f"[Subtitle {job.job_id}] poll error: {e}") + continue + + # 提交 + acquired = await slots.acquire(SLOT_KEY, job.job_id, MAX_SLOTS) + if not acquired: + continue + + try: + service = VolcengineCaptionService() + if mode == "auto_align": + if not audio_text: + raise ValueError("auto_align 模式需要提供 audio_text") + volc_task_id = await service.submit_auto_align_task( + audio_url=video_path, + audio_text=audio_text, + ) + else: + volc_task_id = await service.submit_caption_task( + audio_url=video_path, language=language + ) + if not volc_task_id: + raise ValueError("未返回任务ID") + params["volc_task_id"] = volc_task_id + changes.append(StateChange(job_id=job.job_id, field_path="params", value=params)) + changes.append( + StateChange(job_id=job.job_id, field_path="message", value="字幕任务已提交") + ) + except Exception as e: + await slots.release(SLOT_KEY, job.job_id) + changes.append(StateChange(job_id=job.job_id, field_path="status", value="failed")) + changes.append( + StateChange(job_id=job.job_id, field_path="message", value=str(e)[:200]) + ) + changes.append( + StateChange(job_id=job.job_id, field_path="error", value=str(e)[:500]) + ) + + return changes diff --git a/python-api/app/scheduler/handlers/video_handler.py b/python-api/app/scheduler/handlers/video_handler.py new file mode 100644 index 0000000..f3143bc --- /dev/null +++ b/python-api/app/scheduler/handlers/video_handler.py @@ -0,0 +1,425 @@ +""" +Video 任务处理器 +=============== + +管理 Kling 视频生成(含 segment 和 empty_shot)的提交、轮询、下载。 +占用全局槽位:18 +""" + +import asyncio +import contextlib +import json +import logging +import tempfile +import uuid +from pathlib import Path +from typing import Any + +import aiohttp + +from app.ai.providers.klingai_provider import KlingAIProvider, KlingPromptBuilder +from app.config import get_settings +from app.core.config_loader import get_config_loader +from app.scheduler.handlers.base import AsyncHandler +from app.scheduler.models import StateChange +from app.scheduler.registry import JobRegistry +from app.scheduler.slot_manager import SlotManager +from app.services.qiniu_service import get_qiniu_service + +logger = logging.getLogger(__name__) + +SLOT_KEY = "kling:video_slots" +MAX_SLOTS = 18 + + +class VideoHandler(AsyncHandler): + name = "video" + slot_key = SLOT_KEY + max_slots = MAX_SLOTS + + def __init__(self): + self._provider: KlingAIProvider | None = None + + async def _get_provider(self) -> KlingAIProvider: + if self._provider is None: + settings = get_settings() + config_loader = get_config_loader() + platform = config_loader.get_platform("klingai") + self._provider = KlingAIProvider( + { + "access_key": settings.KLINGAI_ACCESS_KEY or "", + "secret_key": settings.KLINGAI_SECRET_KEY or "", + "base_url": ( + platform.base_url if platform else "https://api-beijing.klingai.com" + ), + } + ) + return self._provider + + def _get_project_video_dir(self, project_id: str) -> Path: + video_dir = Path.home() / "Documents" / "Meijiaka" / "projects" / project_id / "videos" + video_dir.mkdir(parents=True, exist_ok=True) + return video_dir + + async def _download_video(self, video_url: str, local_path: Path) -> None: + async with aiohttp.ClientSession() as session, session.get(video_url) as resp: + resp.raise_for_status() + local_path.write_bytes(await resp.read()) + + async def _download_image(self, image_url: str, local_path: Path) -> None: + async with aiohttp.ClientSession() as session, session.get(image_url) as resp: + resp.raise_for_status() + local_path.write_bytes(await resp.read()) + + async def _poll_image_task(self, provider: KlingAIProvider, image_task_id: str) -> str: + """轮询文生图任务,返回图片 URL""" + timeout = 600 + start = asyncio.get_event_loop().time() + while True: + if asyncio.get_event_loop().time() - start > timeout: + raise TimeoutError("文生图轮询超时") + result = await provider.get_image_task(image_task_id) + status = result.get("task_status", "unknown") + if status == "succeed": + images = result.get("task_result", {}).get("images", []) + if images and images[0].get("url"): + return images[0]["url"] + raise Exception("文生图成功但未返回图片 URL") + if status == "failed": + raise Exception(result.get("task_status_msg", "文生图失败")) + await asyncio.sleep(5) + + async def tick( + self, jobs: list[Any], registry: JobRegistry, slots: SlotManager + ) -> list[StateChange]: + changes: list[StateChange] = [] + provider = await self._get_provider() + + for job in jobs: + job_changes = await self._process_job(job, registry, slots, provider) + changes.extend(job_changes) + + return changes + + async def _process_job( + self, job: Any, registry: JobRegistry, slots: SlotManager, provider: KlingAIProvider + ) -> list[StateChange]: + changes: list[StateChange] = [] + params = job.params or {} + shots = params.get("shots", []) + if isinstance(shots, str): + shots = json.loads(shots) + params["shots"] = shots + if not shots: + changes.append(StateChange(job_id=job.job_id, field_path="status", value="failed")) + changes.append(StateChange(job_id=job.job_id, field_path="error", value="没有镜头数据")) + return changes + + project_id = params.get("project_id", job.job_id) + + # 1. 查询 submitted 状态的 shots + for i, shot in enumerate(shots): + if shot.get("status") != "submitted": + continue + provider_task_id = shot.get("provider_task_id") + if not provider_task_id: + continue + + try: + if shot.get("type") == "segment": + result = await provider.get_omni_video_task(provider_task_id) + else: + result = await provider.get_video_task( + provider_task_id, task_type="image2video" + ) + status = result.get("task_status", "unknown") + except Exception as e: + logger.error(f"[Video {job.job_id}] Query shot {shot['id']} error: {e}") + # 累计查询失败计数 + fail_count = shot.get("query_fail_count", 0) + 1 + if fail_count >= 5: + await slots.release(SLOT_KEY, f"{job.job_id}:{shot['id']}") + shots[i]["status"] = "failed" + shots[i]["error_message"] = f"查询状态连续失败: {e}"[:500] + shots[i]["query_fail_count"] = fail_count + changes.append( + StateChange(job_id=job.job_id, field_path="params", value=params) + ) + else: + shots[i]["query_fail_count"] = fail_count + changes.append( + StateChange(job_id=job.job_id, field_path="params", value=params) + ) + continue + + # 检查超时:超过 2 小时还在 processing 就标记失败 + created_at = result.get("created_at", 0) # KlingAI 返回的是 Unix 毫秒时间戳 + import time + now_ms = int(time.time() * 1000) + if status == "processing" and created_at > 0 and (now_ms - created_at) > 2 * 60 * 60 * 1000: + # 超时 2 小时,标记失败释放槽位 + await slots.release(SLOT_KEY, f"{job.job_id}:{shot['id']}") + shots[i]["status"] = "failed" + shots[i]["error_message"] = "生成超时(超过 2 小时仍在处理中)" + logger.warning(f"[Video {job.job_id}] Shot {shot['id']} timeout, marked as failed") + changes.append(StateChange(job_id=job.job_id, field_path="params", value=params)) + + elif status == "succeed": + await slots.release(SLOT_KEY, f"{job.job_id}:{shot['id']}") + videos = result.get("task_result", {}).get("videos", []) + video_url = videos[0].get("url") if videos else None + if video_url: + shots[i]["video_url"] = video_url + shots[i]["status"] = "completed" + # 完成就立即下载,不用等全部完成 + logger.info(f"[Video {job.job_id}] Shot {shot['id']} completed, downloading...") + await self._download_and_upload(project_id, shots[i]) + else: + shots[i]["status"] = "failed" + shots[i]["error_message"] = "任务成功但未返回视频" + changes.append(StateChange(job_id=job.job_id, field_path="params", value=params)) + + elif status == "failed": + await slots.release(SLOT_KEY, f"{job.job_id}:{shot['id']}") + shots[i]["status"] = "failed" + shots[i]["error_message"] = result.get("task_status_msg", "生成失败")[:500] + changes.append(StateChange(job_id=job.job_id, field_path="params", value=params)) + + # 2. 提交 pending 状态的 shots(填槽),segment 优先于 empty_shot + pending_shots = sorted( + [s for s in shots if s.get("status") == "pending"], + key=lambda s: 0 if s.get("type") == "segment" else 1, + ) + for shot in pending_shots: + slot_id = f"{job.job_id}:{shot['id']}" + acquired = await slots.acquire(SLOT_KEY, slot_id, MAX_SLOTS) + if not acquired: + continue # 当前这个获取失败(槽位满或网络问题),跳过尝试下一个,下次 tick 再重试 + + try: + if shot.get("type") == "segment": + human_id = shot.get("human_id") or params.get("human_id") + if not human_id: + raise ValueError(f"分镜 {shot['id']} 缺少 human_id") + prompt = KlingPromptBuilder.omni_segment( + shot.get("scene", ""), shot.get("voiceover", "") + ) + result = await provider.generate_video_omni( + prompt=prompt, + model="kling-v3-omni", + mode="pro", + aspect_ratio="9:16", + duration=shot.get("duration"), + sound="on", + multi_shot=False, + element_list=[{"element_id": str(human_id)}], + ) + else: + # empty_shot: 文生图 -> 上传七牛 -> 图生视频 + result = await self._submit_empty_shot(shot, provider) + + provider_task_id = result.get("task_id") + if not provider_task_id: + raise ValueError(f"创建任务失败,未返回 provider_task_id: {result}") + + shot["provider_task_id"] = provider_task_id + shot["status"] = "submitted" + logger.info(f"[Video {job.job_id}] Shot {shot['id']} submitted: {provider_task_id}") + except Exception as e: + await slots.release(SLOT_KEY, slot_id) + shot["status"] = "failed" + shot["error_message"] = str(e)[:500] + logger.error(f"[Video {job.job_id}] Submit shot {shot['id']} failed: {e}") + + changes.append(StateChange(job_id=job.job_id, field_path="params", value=params)) + + # 3. 检查是否所有 shots 都完成,做最终汇总 + all_done = all(s.get("status") in ("completed", "failed") for s in shots) + completed = sum(1 for s in shots if s.get("status") == "completed") + failed = sum(1 for s in shots if s.get("status") == "failed") + + if all_done: + # 下载已经在每个分镜完成时处理过了,这里只重试下载失败的 + retry_download_tasks = [ + self._download_and_upload(project_id, shot) + for shot in shots + if shot.get("status") == "completed" + and shot.get("video_url") + and not shot.get("local_path") + ] + if retry_download_tasks: + logger.info(f"[Video {job.job_id}] Final retry downloading {len(retry_download_tasks)} videos...") + await asyncio.gather(*retry_download_tasks, return_exceptions=True) + logger.info(f"[Video {job.job_id}] Retry downloads finished") + # shots 字典已被 _download_and_upload 更新,写回 params + changes.append(StateChange(job_id=job.job_id, field_path="params", value=params)) + + # 下载后重新统计,以反映可能的下载失败 + completed = sum(1 for s in shots if s.get("status") == "completed") + failed = sum(1 for s in shots if s.get("status") == "failed") + + if completed == 0 and failed > 0: + errors = "; ".join( + f"{s.get('id')}: {s.get('error_message')}" + for s in shots + if s.get("error_message") + ) + changes.append(StateChange(job_id=job.job_id, field_path="status", value="failed")) + changes.append( + StateChange( + job_id=job.job_id, + field_path="message", + value=f"全部失败 ({failed}/{len(shots)})", + ) + ) + changes.append(StateChange(job_id=job.job_id, field_path="error", value=errors)) + changes.append( + StateChange(job_id=job.job_id, field_path="completed", value=len(shots)) + ) + changes.append(StateChange(job_id=job.job_id, field_path="total", value=len(shots))) + changes.append(StateChange(job_id=job.job_id, field_path="progress", value=100)) + else: + changes.append( + StateChange(job_id=job.job_id, field_path="status", value="completed") + ) + changes.append( + StateChange( + job_id=job.job_id, + field_path="message", + value=f"完成!成功 {completed},失败 {failed}", + ) + ) + changes.append( + StateChange(job_id=job.job_id, field_path="completed", value=len(shots)) + ) + changes.append(StateChange(job_id=job.job_id, field_path="total", value=len(shots))) + changes.append(StateChange(job_id=job.job_id, field_path="progress", value=100)) + # result 字段包含 shots 汇总(含下载后的 local_path / qiniu_url) + result_data = { + "project_id": project_id, + "completed": completed, + "failed": failed, + "total": len(shots), + "shots": [ + { + "shot_id": s.get("id"), + "type": s.get("type"), + "status": s.get("status"), + "task_id": s.get("provider_task_id"), + "video_url": s.get("video_url"), + "local_path": s.get("local_path"), + "qiniu_url": s.get("qiniu_url"), + "error_message": s.get("error_message"), + } + for s in shots + ], + } + changes.append( + StateChange(job_id=job.job_id, field_path="result", value=result_data) + ) + else: + done_count = completed + failed + changes.append(StateChange(job_id=job.job_id, field_path="status", value="running")) + changes.append( + StateChange( + job_id=job.job_id, + field_path="message", + value=f"{done_count}/{len(shots)} 个镜头处理中", + ) + ) + changes.append(StateChange(job_id=job.job_id, field_path="completed", value=done_count)) + changes.append(StateChange(job_id=job.job_id, field_path="total", value=len(shots))) + + return changes + + async def _submit_empty_shot( + self, shot: dict[str, Any], provider: KlingAIProvider + ) -> dict[str, Any]: + """空镜 shot 的完整提交流程:文生图 -> 上传七牛 -> 图生视频""" + qiniu = get_qiniu_service() + + # 1. 文生图 + image_result = await provider.generate_image( + prompt=shot.get("scene", ""), + model="kling-v3", + aspect_ratio="9:16", + ) + image_task_id = image_result.get("task_id") + if not image_task_id: + raise ValueError(f"文生图创建失败: {image_result}") + + # 2. 轮询图片完成 + image_url = await self._poll_image_task(provider, image_task_id) + + # 3. 下载图片 + temp_dir = Path(tempfile.gettempdir()) / "meijiaka_empty_shot" + temp_dir.mkdir(parents=True, exist_ok=True) + temp_image_path = temp_dir / f"{image_task_id}.jpg" + await self._download_image(image_url, temp_image_path) + + # 4. 上传七牛 + qiniu_result = qiniu.upload_file( + local_path=str(temp_image_path), + file_type="image", + check_duplicate=True, + ) + qiniu_image_url = qiniu_result["url"] + with contextlib.suppress(Exception): + temp_image_path.unlink() + + # 5. 图生视频 + voice_id = shot.get("voice_id") or get_settings().DEFAULT_EMPTY_SHOT_VOICE_ID + prompt = KlingPromptBuilder.empty_shot(shot.get("scene", ""), shot.get("voiceover", "")) + result = await provider.generate_video_image2video( + prompt=prompt, + image_url=qiniu_image_url, + model="kling-v2-6", + mode="pro", + duration=shot.get("duration"), + voice_list=[{"voice_id": voice_id}], + sound="on", + negative_prompt="画外音没有标点的时候不要轻易断句", + ) + return result + + async def _download_and_upload(self, project_id: str, shot: dict[str, Any]) -> None: + """下载视频到本地并上传七牛。直接更新传入的 shot 字典,不操作 Redis。""" + video_url = shot.get("video_url") + if not video_url: + shot["status"] = "failed" + shot["error_message"] = "没有视频URL" + return + + video_dir = self._get_project_video_dir(project_id) + + # 清理同 shot_id 的旧视频文件(避免重新生成后前端缓存不刷新) + import glob as stdlib_glob + + pattern = f"scene_{stdlib_glob.escape(str(shot['id']))}_*.mp4" + for old_file in video_dir.glob(pattern): + try: + old_file.unlink() + logger.info(f"[Video] Removed old file: {old_file}") + except Exception as e: + logger.warning(f"[Video] Failed to remove old file {old_file}: {e}") + + # 使用随机后缀命名,确保前端检测到 filePath 变化并重新加载 + local_path = video_dir / f"scene_{shot['id']}_{uuid.uuid4().hex[:6]}.mp4" + + try: + await self._download_video(video_url, local_path) + shot["local_path"] = str(local_path) + + try: + qiniu = get_qiniu_service() + qiniu_result = qiniu.upload_video(local_path=str(local_path)) + shot["qiniu_url"] = qiniu_result["url"] + except Exception as e: + logger.warning(f"[Video] Shot {shot['id']} upload qiniu failed: {e}") + shot["qiniu_url"] = None + + logger.info(f"[Video] Shot {shot['id']} download/upload done: {local_path}") + except Exception as e: + logger.error(f"[Video] Shot {shot['id']} download failed: {e}") + shot["status"] = "failed" + shot["error_message"] = f"下载失败: {e}"[:500] diff --git a/python-api/app/scheduler/main.py b/python-api/app/scheduler/main.py new file mode 100644 index 0000000..5f4bdba --- /dev/null +++ b/python-api/app/scheduler/main.py @@ -0,0 +1,48 @@ +""" +Async Engine 独立进程入口 +========================= + +usage: python -m app.scheduler.main +""" + +import asyncio +import logging +import sys + +from app.scheduler.engine import AsyncEngine +from app.scheduler.handlers.avatar_handler import AvatarHandler +from app.scheduler.handlers.copy_handler import CopyHandler +from app.scheduler.handlers.image_handler import ImageHandler +from app.scheduler.handlers.script_handler import ScriptHandler +from app.scheduler.handlers.subtitle_handler import SubtitleHandler +from app.scheduler.handlers.video_handler import VideoHandler + +logger = logging.getLogger("scheduler") + + +def setup_logging() -> None: + log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + logging.basicConfig( + level=logging.INFO, + format=log_format, + handlers=[logging.StreamHandler(sys.stdout)], + ) + + +async def main() -> None: + setup_logging() + engine = AsyncEngine() + engine.register(VideoHandler()) + engine.register(AvatarHandler()) + engine.register(ImageHandler()) + engine.register(SubtitleHandler()) + engine.register(CopyHandler()) + engine.register(ScriptHandler()) + await engine.run_forever(interval=10.0, min_interval=2.0) + + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + logger.info("Scheduler stopped by user") diff --git a/python-api/app/scheduler/models.py b/python-api/app/scheduler/models.py new file mode 100644 index 0000000..36d7200 --- /dev/null +++ b/python-api/app/scheduler/models.py @@ -0,0 +1,47 @@ +""" +Scheduler 内部数据模型 +""" + +import json +from dataclasses import dataclass, field +from typing import Any + +from app.schemas.enums import JobStatus + + +@dataclass +class JobRecord: + """调度器内部使用的作业记录""" + + job_id: str + job_type: str + user_id: str + project_id: str = "" + status: JobStatus = field(default=JobStatus.PENDING) + progress: int = 0 + message: str = "等待执行..." + completed: int = 0 + total: int = 0 + result: dict[str, Any] = field(default_factory=dict) + error: str | None = None + params: dict[str, Any] | Any = field(default_factory=dict) + created_at: str = "" + + +@dataclass(frozen=True) +class StateChange: + """状态变更记录,供 Registry 批量写入""" + + job_id: str + field_path: str # 如 "shots.0.status" 或 "status" + value: Any + + def to_redis_command(self) -> tuple[str, str, str]: + key = f"job:{self.job_id}" + if isinstance(self.value, dict | list): + value = json.dumps(self.value, ensure_ascii=False) + elif self.value is None: + value = "" + else: + value = str(self.value) + return key, self.field_path, value diff --git a/python-api/app/scheduler/registry.py b/python-api/app/scheduler/registry.py new file mode 100644 index 0000000..69a9d7f --- /dev/null +++ b/python-api/app/scheduler/registry.py @@ -0,0 +1,143 @@ +""" +作业注册表 - Redis 运行时状态读写 +================================== + +所有 running 作业的状态统一存储在 Redis 中,供 Scheduler Tick 读取、更新。 +""" + +import json +import logging +from datetime import UTC +from typing import Any + +from redis.asyncio import Redis + +from app.scheduler.models import JobRecord + +logger = logging.getLogger(__name__) + +KEY_RUNNING_SET = "scheduler:running_tasks" + + +def _job_key(job_id: str) -> str: + return f"job:{job_id}" + + +class JobRegistry: + """基于 Redis 的作业注册表""" + + def __init__(self, redis: Redis): + self.redis = redis + + async def create( + self, + job_id: str, + job_type: str, + user_id: str, + status: str = "pending", + params: dict[str, Any] | None = None, + ttl: int = 86400, + ) -> None: + """创建新的作业记录""" + from datetime import datetime + + data = { + "type": job_type, + "user_id": user_id, + "status": status, + "progress": "0", + "message": "等待执行...", + "completed": "0", + "total": "0", + "created_at": datetime.now(UTC).isoformat(), + } + if params: + data["params"] = json.dumps(params, ensure_ascii=False) + + await self.redis.hset(_job_key(job_id), mapping=data) + await self.redis.expire(_job_key(job_id), ttl) + logger.debug(f"Registry created: {job_id}, type={job_type}") + + async def update(self, job_id: str, **fields: Any) -> None: + """更新作业字段""" + mapping: dict[str, str] = {} + for key, value in fields.items(): + if isinstance(value, dict | list): + mapping[key] = json.dumps(value, ensure_ascii=False) + elif value is None: + mapping[key] = "" + else: + mapping[key] = str(value) + await self.redis.hset(_job_key(job_id), mapping=mapping) + logger.debug(f"Registry updated: {job_id}, fields={list(fields.keys())}") + + async def get(self, job_id: str) -> JobRecord | None: + """读取完整作业记录""" + data = await self.redis.hgetall(_job_key(job_id)) + if not data: + return None + + def _parse(key: str, raw: str) -> Any: + if key in ("result", "params") and raw: + try: + return json.loads(raw) + except json.JSONDecodeError: + return raw + if key in ("progress", "completed", "total"): + try: + return int(raw) + except ValueError: + return 0 + return raw + + parsed = {k: _parse(k, v) for k, v in data.items()} + job_type = parsed.get("type", "") + params_raw = parsed.get("params", {}) + params = params_raw if isinstance(params_raw, dict) else {} + + return JobRecord( + job_id=job_id, + job_type=job_type, + user_id=parsed.get("user_id", ""), + project_id=str(params.get("project_id", "")), + status=parsed.get("status", "unknown"), + progress=parsed.get("progress", 0), + message=parsed.get("message", ""), + completed=parsed.get("completed", 0), + total=parsed.get("total", 0), + result=parsed.get("result", {}), + error=parsed.get("error"), + params=params, + created_at=parsed.get("created_at", ""), + ) + + async def add_running(self, job_id: str) -> None: + """将作业标记为 running(加入全局 running 集合)""" + await self.redis.sadd(KEY_RUNNING_SET, job_id) + + async def remove_running(self, job_id: str) -> None: + """将作业从全局 running 集合移除""" + await self.redis.srem(KEY_RUNNING_SET, job_id) + + async def get_running_job_ids(self) -> list[str]: + """获取所有 running 的作业 ID 列表""" + members = await self.redis.smembers(KEY_RUNNING_SET) + return list(members) + + async def list_running_by_user(self, user_id: str) -> list[JobRecord]: + """获取指定用户的所有 running 作业""" + job_ids = await self.get_running_job_ids() + if not job_ids: + return [] + + results: list[JobRecord] = [] + for job_id in job_ids: + job = await self.get(job_id) + if job and job.user_id == user_id: + results.append(job) + return results + + async def delete(self, job_id: str) -> None: + """删除作业记录""" + await self.redis.delete(_job_key(job_id)) + await self.redis.srem(KEY_RUNNING_SET, job_id) diff --git a/python-api/app/scheduler/slot_manager.py b/python-api/app/scheduler/slot_manager.py new file mode 100644 index 0000000..a14a534 --- /dev/null +++ b/python-api/app/scheduler/slot_manager.py @@ -0,0 +1,63 @@ +""" +全局并发槽位管理器 +================== + +基于 Redis SET + Lua 脚本实现严格原子的槽位申请与释放。 +""" + +import logging + +from redis.asyncio import Redis + +logger = logging.getLogger(__name__) + +# Lua 脚本:原子执行 SADD -> SCARD -> 条件 SREM +_ACQUIRE_LUA = """ +local key = KEYS[1] +local slot_id = ARGV[1] +local max_slots = tonumber(ARGV[2]) +redis.call('sadd', key, slot_id) +local count = redis.call('scard', key) +if count > max_slots then + redis.call('srem', key, slot_id) + return 0 +end +redis.call('expire', key, 1800) +return 1 +""" + + +class SlotManager: + """全局并发槽位管理器""" + + def __init__(self, redis: Redis): + self.redis = redis + + async def acquire(self, slot_key: str, slot_id: str, max_slots: int) -> bool: + """申请一个槽位。返回 True 表示成功,False 表示槽位已满。""" + try: + result = await self.redis.eval(_ACQUIRE_LUA, 1, slot_key, slot_id, str(max_slots)) + acquired = result == 1 + if acquired: + logger.debug(f"Slot acquired: {slot_key}/{slot_id} (max={max_slots})") + else: + logger.debug(f"Slot full: {slot_key}/{slot_id} (max={max_slots})") + return acquired + except Exception as e: + logger.warning(f"Slot acquire error: {slot_key}/{slot_id}: {e}") + return False + + async def release(self, slot_key: str, slot_id: str) -> None: + """释放一个槽位。""" + try: + await self.redis.srem(slot_key, slot_id) + logger.debug(f"Slot released: {slot_key}/{slot_id}") + except Exception as e: + logger.warning(f"Slot release error: {slot_key}/{slot_id}: {e}") + + async def count(self, slot_key: str) -> int: + """获取当前已占用的槽位数量。""" + try: + return await self.redis.scard(slot_key) + except Exception: + return 0 diff --git a/python-api/app/schemas/__init__.py b/python-api/app/schemas/__init__.py new file mode 100644 index 0000000..abd66d8 --- /dev/null +++ b/python-api/app/schemas/__init__.py @@ -0,0 +1,78 @@ +""" +Schema 导出 +=========== +""" + +from app.schemas.auth import LoginResponse, MobileLoginRequest, TokenPayload, UserInfo +from app.schemas.common import ( + ApiErrorResponse, + ApiResponse, + PaginatedData, + PaginationParams, + error_response, + success_response, +) +from app.schemas.enums import ( + AvatarCloneStatus, + JobStatus, + KlingTaskStatus, + SegmentStatus, +) +from app.schemas.job import ( + AvatarCloneJobParams, + CopyJobParams, + ImageJobParams, + JobParams, + ScriptJobParams, + SubtitleJobParams, + VideoJobParams, +) +from app.schemas.script import ( + GenerateScriptRequest, + ModelHealthInfo, + ModelHealthResponse, + PolishRequest, + ScriptGenerationEvent, + ScriptShot, + TestModelRequest, + TestModelResponse, +) +from app.schemas.segment import Segment + +__all__ = [ + # Common + "ApiResponse", + "ApiErrorResponse", + "PaginatedData", + "PaginationParams", + "success_response", + "error_response", + # Auth + "MobileLoginRequest", + "LoginResponse", + "UserInfo", + "TokenPayload", + # Enums + "JobStatus", + "SegmentStatus", + "AvatarCloneStatus", + "KlingTaskStatus", + # Segment / Job + "Segment", + "VideoJobParams", + "ImageJobParams", + "SubtitleJobParams", + "CopyJobParams", + "AvatarCloneJobParams", + "ScriptJobParams", + "JobParams", + # Script + "GenerateScriptRequest", + "PolishRequest", + "ScriptGenerationEvent", + "ScriptShot", + "ModelHealthInfo", + "ModelHealthResponse", + "TestModelRequest", + "TestModelResponse", +] diff --git a/python-api/app/schemas/auth.py b/python-api/app/schemas/auth.py new file mode 100644 index 0000000..4c17634 --- /dev/null +++ b/python-api/app/schemas/auth.py @@ -0,0 +1,36 @@ +""" +认证相关 Schema +=============== +""" + +from pydantic import BaseModel, Field + + +class MobileLoginRequest(BaseModel): + """手机号登录请求""" + + mobile: str = Field(..., description="手机号", min_length=11, max_length=20) + nickname: str | None = Field(None, description="用户昵称", max_length=64) + + +class UserInfo(BaseModel): + """用户信息""" + + id: str = Field(..., description="用户 ID") + nickname: str = Field(..., description="用户昵称") + avatar: str = Field(default="", description="头像 URL") + + +class LoginResponse(BaseModel): + """登录响应""" + + token: str = Field(..., description="JWT 访问令牌") + user: UserInfo = Field(..., description="用户信息") + + +class TokenPayload(BaseModel): + """Token 载荷""" + + sub: str | None = Field(None, description="用户 ID") + mobile: str | None = Field(None, description="手机号") + exp: int | None = Field(None, description="过期时间戳") diff --git a/python-api/app/schemas/avatar.py b/python-api/app/schemas/avatar.py new file mode 100644 index 0000000..3309ec9 --- /dev/null +++ b/python-api/app/schemas/avatar.py @@ -0,0 +1,33 @@ +""" +Avatar Schema +============= + +形象克隆相关的 Pydantic 模型,用于 CRUD 强类型化。 +""" + +from pydantic import BaseModel, Field + +from app.schemas.enums import AvatarCloneStatus + + +class AvatarCreate(BaseModel): + """创建形象记录""" + + id: str + user_id: str + name: str = Field(..., min_length=1, max_length=64) + video_url: str = Field(..., min_length=1) + status: AvatarCloneStatus = Field(default=AvatarCloneStatus.PENDING) + + +class AvatarUpdate(BaseModel): + """更新形象记录""" + + name: str | None = None + status: AvatarCloneStatus | None = None + provider_voice_job_id: str | None = None + provider_element_job_id: str | None = None + provider_element_id: int | None = None + voice_id: str | None = None + trial_url: str | None = None + fail_reason: str | None = None diff --git a/python-api/app/schemas/caption.py b/python-api/app/schemas/caption.py new file mode 100644 index 0000000..55b8335 --- /dev/null +++ b/python-api/app/schemas/caption.py @@ -0,0 +1,98 @@ +""" +字幕生成 Schema +=============== + +火山引擎音视频字幕服务的请求/响应模型。 +""" + +from __future__ import annotations + +from pydantic import BaseModel, Field + + +class CaptionWord(BaseModel): + """单个字/词的时间轴信息""" + + text: str = Field(description="字/词内容") + start_time: int = Field(description="开始时间(毫秒)") + end_time: int = Field(description="结束时间(毫秒)") + + +class CaptionUtterance(BaseModel): + """一句话/一段字幕的时间轴信息""" + + text: str = Field(description="文本内容") + start_time: int = Field(description="开始时间(毫秒)") + end_time: int = Field(description="结束时间(毫秒)") + words: list[CaptionWord] | None = Field(default_factory=list, description="字词级时间轴") + + +class CaptionTaskResponse(BaseModel): + """字幕任务提交响应""" + + task_id: str = Field(description="任务ID") + status: str = Field(description="任务状态: pending/processing/completed/failed") + + +class CaptionResult(BaseModel): + """字幕生成结果""" + + code: int = Field(description="状态码: 0=成功, 2000=处理中") + message: str = Field(description="状态信息") + duration: float = Field(description="音频时长(秒)") + utterances: list[CaptionUtterance] | None = Field( + default_factory=list, description="字幕时间轴列表" + ) + + +class CaptionSubmitRequest(BaseModel): + """字幕生成任务提交请求""" + + audio_url: str = Field(..., description="音频/视频文件URL") + language: str = Field( + "zh-CN", + description="语言: zh-CN, en-US, ja-JP, ko-KR, es-MX, ru-RU, fr-FR, yue, wuu, nan, ug", + ) + caption_type: str = Field( + "auto", description="识别类型: auto(自动), speech(说话), singing(歌词)" + ) + use_punc: bool = Field(True, description="自动标点: True/False") + use_itn: bool = Field(True, description="数字转换: True(中文数字转阿拉伯数字)") + words_per_line: int = Field(46, ge=1, le=100, description="每行字数") + max_lines: int = Field(1, ge=1, le=5, description="每屏行数") + + +class CaptionQueryRequest(BaseModel): + """字幕任务查询请求""" + + task_id: str = Field(..., description="任务ID") + blocking: bool = Field(True, description="是否阻塞等待结果") + + +class AutoAlignSubmitRequest(BaseModel): + """自动字幕打轴任务提交请求""" + + audio_url: str = Field(..., description="音频/视频文件URL") + audio_text: str = Field(..., description="要打轴的字幕文本") + caption_type: str = Field("speech", description="识别类型: speech(说话), singing(歌词)") + sta_punc_mode: int = Field( + 3, ge=1, le=3, description="标点模式: 1=省略句末, 2=空格代替, 3=保留完整" + ) + + +class AutoAlignResult(BaseModel): + """自动字幕打轴结果""" + + code: int = Field(description="状态码: 0=成功, 2000=处理中") + message: str = Field(description="状态信息") + duration: float = Field(description="音频时长(秒)") + utterances: list[CaptionUtterance] | None = Field( + default_factory=list, description="打轴后的字幕时间轴" + ) + + +class SrtSubtitleResponse(BaseModel): + """SRT 字幕格式响应""" + + srt_content: str = Field(description="SRT 格式字幕内容") + utterances: list[CaptionUtterance] = Field(description="原始时间轴数据") diff --git a/python-api/app/schemas/common.py b/python-api/app/schemas/common.py new file mode 100644 index 0000000..89c581b --- /dev/null +++ b/python-api/app/schemas/common.py @@ -0,0 +1,78 @@ +""" +通用响应格式 Schema +================== + +与前端 ApiResponse 保持一致: +{ code: number; data: T; message: string } +""" + +from typing import Any, Generic, TypeVar + +from pydantic import BaseModel, Field + +T = TypeVar("T") + + +class ApiResponse(BaseModel, Generic[T]): + """ + 统一 API 响应格式 + + Attributes: + code: HTTP 状态码(200 表示成功) + data: 响应数据(泛型) + message: 提示信息 + """ + + code: int = Field(default=200, description="状态码,200 表示成功") + data: T | None = Field(default=None, description="响应数据") + message: str = Field(default="success", description="提示信息") + + class Config: + json_schema_extra = { + "example": { + "code": 200, + "data": {}, + "message": "success", + } + } + + +class PaginatedData(BaseModel, Generic[T]): + """分页数据包装""" + + items: list[T] = Field(description="数据列表") + total: int = Field(description="总数") + page: int = Field(description="当前页码") + page_size: int = Field(description="每页数量") + has_more: bool = Field(description="是否有更多") + + +class PaginationParams(BaseModel): + """分页请求参数""" + + page: int = Field(default=1, ge=1, description="页码") + page_size: int = Field(default=20, ge=1, le=100, description="每页数量") + + @property + def offset(self) -> int: + return (self.page - 1) * self.page_size + + +class ApiErrorResponse(BaseModel): + """错误响应格式""" + + code: int = Field(description="错误码") + message: str = Field(description="错误信息") + detail: dict[str, Any] | None = Field(default=None, description="详细错误信息") + + +def success_response(data: T | None = None, message: str = "success") -> ApiResponse[T]: + """构造成功响应""" + return ApiResponse(code=200, data=data, message=message) + + +def error_response( + code: int, message: str, detail: dict[str, Any] | None = None +) -> ApiErrorResponse: + """构造错误响应""" + return ApiErrorResponse(code=code, message=message, detail=detail) diff --git a/python-api/app/schemas/enums.py b/python-api/app/schemas/enums.py new file mode 100644 index 0000000..7b333a0 --- /dev/null +++ b/python-api/app/schemas/enums.py @@ -0,0 +1,49 @@ +""" +业务枚举定义 +============ + +所有跨层使用的状态枚举集中定义在此,避免字符串硬编码。 +""" + +from enum import Enum + + +class JobStatus(str, Enum): + """调度器作业状态""" + + PENDING = "pending" + RUNNING = "running" + COMPLETED = "completed" + FAILED = "failed" + + +class SegmentStatus(str, Enum): + """视频分镜处理状态""" + + PENDING = "pending" + SUBMITTED = "submitted" + PROCESSING = "processing" + COMPLETED = "completed" + FAILED = "failed" + + +class AvatarCloneStatus(str, Enum): + """形象克隆状态机""" + + PENDING = "pending" + VOICE_PROCESSING = "voice_processing" + VOICE_FAILED = "voice_failed" + ELEMENT_PENDING = "element_pending" + ELEMENT_PROCESSING = "element_processing" + ELEMENT_FAILED = "element_failed" + SUCCEED = "succeed" + TIMEOUT = "timeout" + + +class KlingTaskStatus(str, Enum): + """Kling AI 供应商任务状态(仅限 Provider 层使用)""" + + SUBMITTED = "submitted" + PROCESSING = "processing" + SUCCEED = "succeed" + FAILED = "failed" diff --git a/python-api/app/schemas/job.py b/python-api/app/schemas/job.py new file mode 100644 index 0000000..728c567 --- /dev/null +++ b/python-api/app/schemas/job.py @@ -0,0 +1,73 @@ +""" +调度器作业参数 Schema +===================== + +定义 Scheduler (Layer 4) 使用的强类型参数模型, +取代裸 `dict[str, Any]`,根除类型漂移。 +""" + +from pydantic import BaseModel + +from app.schemas.segment import Segment + + +class VideoJobParams(BaseModel): + """视频生成作业参数""" + + project_id: str + user_id: str + human_id: str | None = None + segments: list[Segment] + + +class ImageJobParams(BaseModel): + """图片生成作业参数""" + + project_id: str + user_id: str + prompt: str + image_type: str = "cover" + reference_image: str | None = None + human_id: str | None = None + + +class SubtitleJobParams(BaseModel): + """字幕生成作业参数""" + + project_id: str + video_path: str + language: str = "zh" + mode: str = "caption" # "caption" | "auto_align" + audio_text: str | None = None # auto_align 模式时需要 + + +class CopyJobParams(BaseModel): + """文案提取作业参数""" + + video_url: str + + +class AvatarCloneJobParams(BaseModel): + """形象克隆作业参数""" + + avatar_id: str + name: str + video_url: str + + +class ScriptJobParams(BaseModel): + """脚本生成作业参数""" + + topic: str + style: str + duration: int + + +JobParams = ( + VideoJobParams + | ImageJobParams + | SubtitleJobParams + | CopyJobParams + | AvatarCloneJobParams + | ScriptJobParams +) diff --git a/python-api/app/schemas/script.py b/python-api/app/schemas/script.py new file mode 100644 index 0000000..1c198a8 --- /dev/null +++ b/python-api/app/schemas/script.py @@ -0,0 +1,98 @@ +""" +脚本相关 Schema +=============== +""" + +from typing import Any + +from pydantic import BaseModel, Field + +from app.schemas.segment import Segment + +ScriptShot = Segment + + +class GenerateScriptRequest(BaseModel): + """生成脚本请求""" + + topic: str = Field(..., description="创作主题/灵感", min_length=1, max_length=1000) + duration: int = Field(default=45, ge=30, le=180, description="视频时长(秒)") + script_type: str = Field(default="干货型", description="脚本类型") + model: str | None = Field(None, description="指定模型(可选)") + + +class PolishRequest(BaseModel): + """润色请求""" + + content: str = Field(..., description="待润色内容", min_length=1) + polish_type: str = Field(default="voiceover", description="润色类型:scene / voiceover") + shot_type: str | None = Field( + default="segment", + description="镜头类型:segment(分镜) / empty_shot(空镜),用于画面润色时区分", + ) + + +class ScriptGenerationEvent(BaseModel): + """ + 脚本生成 SSE 事件 + + 与前端 ScriptGenerationEvent 对应 + + 事件类型说明: + - start: 初始化(0-5%) + - analyzing: 分析主题(5-15%) + - generating: AI 生成中(15-85%) + - validating: 验证格式(85-92%) + - parsing: 解析分镜(92-98%) + - finalizing: 整理结果(98-100%) + - complete: 完成(100%) + - error: 错误 + """ + + type: str = Field( + ..., + description="事件类型:start / analyzing / generating / validating / parsing / finalizing / complete / error", + ) + progress: int = Field(default=0, ge=0, le=100, description="进度百分比") + message: str = Field(..., description="状态描述") + result: list[Any] | None = Field(None, description="生成的分镜结果(complete 时)") + extracted_info: dict[str, Any] | None = Field( + None, description="提取的视频信息(complete 时,如果是视频链接)" + ) + + +class ModelHealthInfo(BaseModel): + """模型健康信息""" + + id: str = Field(..., description="模型 ID") + name: str = Field(..., description="模型名称") + is_available: bool = Field(..., description="是否可用") + response_time: float = Field(..., description="响应时间(毫秒)") + last_error: str | None = Field(None, description="上次错误信息") + + +class ModelHealthResponse(BaseModel): + """模型健康检查响应""" + + status: str = Field(..., description="整体状态:healthy / unhealthy / error") + models: list[ModelHealthInfo] = Field(..., description="各模型状态") + recommended_model: ModelHealthInfo | None = Field(None, description="推荐的模型") + total_models: int = Field(..., description="模型总数") + available_models: int = Field(..., description="可用模型数") + error: str | None = Field(None, description="错误信息") + + +class TestModelRequest(BaseModel): + """测试模型请求""" + + model_id: str | None = Field(None, description="要测试的模型 ID") + + +class TestModelResponse(BaseModel): + """测试模型响应""" + + success: bool = Field(..., description="是否成功") + model: str = Field(..., description="模型名称") + response_time: float | None = Field(None, description="响应时间(毫秒)") + error: str | None = Field(None, description="错误信息") + checked_at: str | None = Field(None, description="检查时间 ISO 格式") diff --git a/python-api/app/schemas/segment.py b/python-api/app/schemas/segment.py new file mode 100644 index 0000000..604bb53 --- /dev/null +++ b/python-api/app/schemas/segment.py @@ -0,0 +1,48 @@ +""" +分镜(Segment)统一 Schema +========================== + +业务层唯一分镜模型,取代以下历史重复定义: +- ScriptShot(script.py) +- ShotData(api/v1/video.py) +- ShotTask(services/kling_video_service.py) +- ShotUnit(scheduler/models.py) +""" + +from typing import Literal + +from pydantic import BaseModel, Field + +from app.schemas.enums import SegmentStatus + + +class Segment(BaseModel): + """视频分镜/镜头定义 + + 术语说明: + - segment: 分镜(带数字人) + - empty_shot: 空镜(无数字人) + """ + + id: str = Field(..., description="分镜ID") + type: Literal["segment", "empty_shot"] = Field( + default="segment", description="分镜类型: segment(分镜) 或 empty_shot(空镜)" + ) + scene: str = Field(default="", description="场景描述/画面描述") + voiceover: str = Field(default="", description="配音文案(空镜可为空)") + duration: int | None = Field(default=None, description="时长(秒)") + human_id: str | None = Field(default=None, description="数字人主体ID") + voice_id: str | None = Field(default=None, description="音色ID(空镜时使用)") + status: SegmentStatus = Field(default=SegmentStatus.PENDING) + provider_task_id: str | None = Field( + default=None, description="供应商任务ID(如 Kling task_id)" + ) + video_url: str | None = Field(default=None, description="生成后的视频URL") + local_path: str | None = Field(default=None, description="本地视频路径") + qiniu_url: str | None = Field(default=None, description="七牛云URL") + error_message: str | None = Field(default=None, description="错误信息") + stage: str | None = Field( + default=None, description="内部处理阶段(如 image_generating / video_processing)" + ) + image_task_id: str | None = Field(default=None, description="空镜文生图任务ID(内部使用)") + query_fail_count: int = Field(default=0, description="查询失败计数") diff --git a/python-api/app/services/__init__.py b/python-api/app/services/__init__.py new file mode 100644 index 0000000..0a4f867 --- /dev/null +++ b/python-api/app/services/__init__.py @@ -0,0 +1,14 @@ +""" +服务层导出 +========== +""" + +from app.services.kling_video_service import KlingVideoService, get_kling_video_service +from app.services.script_service import ScriptService, get_script_service + +__all__ = [ + "ScriptService", + "get_script_service", + "KlingVideoService", + "get_kling_video_service", +] diff --git a/python-api/app/services/ai_response_utils.py b/python-api/app/services/ai_response_utils.py new file mode 100644 index 0000000..6321dcd --- /dev/null +++ b/python-api/app/services/ai_response_utils.py @@ -0,0 +1,331 @@ +""" +AI 响应处理工具 +=============== + +提供安全的 AI 响应解析、验证和清洗功能。 +这是 AI 输出和后端/前端之间的防火墙。 +""" + +import json +import logging +import re +from typing import Any + +logger = logging.getLogger(__name__) + + +def extract_json_from_markdown(content: str) -> str | None: + """ + 从 Markdown 代码块中提取 JSON 字符串 + + 支持格式: + - ```json {...} ``` + - ``` {...} ``` + - 纯 JSON 文本 + + Args: + content: 原始内容 + + Returns: + 提取的 JSON 字符串,如果无法提取则返回 None + """ + if not content: + return None + + content = content.strip() + + # 匹配 ```json ... ``` 或 ``` ... ``` + pattern = r"```(?:json)?\s*([\s\S]*?)\s*```" + matches = re.findall(pattern, content) + + if matches: + # 取最后一个匹配(避免前面有示例代码) + result = matches[-1].strip() + return result if result else None + + # 如果没有代码块,返回原始内容 + return content + + +def sanitize_string(value: Any, max_length: int = 5000) -> str | None: + """ + 清洗字符串值 + + - 去除 HTML 标签 + - 去除控制字符 + - 标准化空白字符 + - 截断超长内容 + + Args: + value: 原始值 + max_length: 最大长度 + + Returns: + 清洗后的字符串 + """ + if value is None: + return None + + # 转换为字符串 + text = str(value) + + # 去除 HTML 标签 + text = re.sub(r"<[^>]+>", "", text) + + # 去除控制字符(保留换行和制表符) + text = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]", "", text) + + # 标准化空白字符 + text = re.sub(r"[\t ]+", " ", text) + text = re.sub(r"\n+", "\n", text) + text = text.strip() + + # 截断超长内容 + if len(text) > max_length: + logger.warning(f"内容被截断: {len(text)} -> {max_length} 字符") + text = text[:max_length] + "..." + + return text + + +def parse_duration(duration_value: Any) -> str: + """ + 解析时长字段 + + 支持格式: + - 数字 (5) -> "5s" + - 字符串带单位 ("5s", "5秒") -> "5s" + - 其他 -> "5s" (默认) + + Args: + duration_value: 原始时长值 + + Returns: + 标准化的时长字符串 + """ + if duration_value is None: + return "5s" + + # 如果是数字,直接加 s + if isinstance(duration_value, int | float): + seconds = max(1, min(int(duration_value), 300)) # 限制 1-300 秒 + return f"{seconds}s" + + # 如果是字符串 + text = str(duration_value).strip().lower() + + # 提取数字 + match = re.search(r"(\d+)", text) + if match: + seconds = int(match.group(1)) + seconds = max(1, min(seconds, 300)) + return f"{seconds}s" + + return "5s" + + +def validate_and_normalize_shots(raw_data: Any) -> list[dict[str, Any]]: + """ + 验证并标准化分镜数据 + + 这是一个防御性函数,处理各种可能的 AI 返回格式: + - 列表格式: [{...}, {...}] + - 包装格式: {"shots": [...]} -> 提取 shots + - 单对象格式: {...} -> 包装为列表 + - 无效格式: 返回空列表 + + Args: + raw_data: AI 返回的原始数据 + + Returns: + 标准化的分镜列表 + """ + if raw_data is None: + logger.warning("AI 返回数据为空") + return [] + + shots = [] + + # 处理字典格式(可能是包装对象) + if isinstance(raw_data, dict): + # 尝试提取常见的包装字段 + for key in ["shots", "data", "segments", "script", "result", "list"]: + if key in raw_data and isinstance(raw_data[key], list): + shots = raw_data[key] + logger.info(f"从字典字段 '{key}' 提取到 {len(shots)} 个分镜") + break + else: + # 没有列表字段,将整个字典作为一个分镜 + logger.info("将字典作为单个分镜处理") + shots = [raw_data] + + # 处理列表格式 + elif isinstance(raw_data, list): + shots = raw_data + + # 其他格式无法处理 + else: + logger.error(f"无法处理的 AI 返回格式: {type(raw_data)}") + return [] + + # 验证并标准化每个分镜 + normalized_shots = [] + for idx, item in enumerate(shots): + if not isinstance(item, dict): + logger.warning(f"跳过非字典分镜 (索引 {idx}): {type(item)}") + continue + + # 字段映射和清洗 + normalized: dict[str, Any] = { + "id": str(idx + 1), # 强制按索引递增,Segment 模型要求 str + "type": "segment", # 默认类型 + "scene": None, + "voiceover": "", + "duration": 5, # Segment 模型要求 int(秒) + } + + # 提取 ID(支持字符串和数字,最终转为 str) + raw_id = item.get("id", idx + 1) + try: + normalized["id"] = str(int(raw_id)) + except (ValueError, TypeError): + normalized["id"] = str(idx + 1) + + # 提取类型 + raw_type = item.get("type", "segment") + if isinstance(raw_type, str): + normalized["type"] = raw_type.strip().lower() + + # 提取场景描述(支持多种字段名) + scene = ( + item.get("scene") + or item.get("prompt") + or item.get("description") + or item.get("image_prompt") + or item.get("visual") + ) + normalized["scene"] = sanitize_string(scene, max_length=2000) + + # 提取配音文案(支持多种字段名) + voiceover = ( + item.get("voiceover") + or item.get("text") + or item.get("content") + or item.get("narration") + or item.get("script") + ) + normalized["voiceover"] = sanitize_string(voiceover, max_length=2000) or "" + + # 提取时长(Segment 模型要求 int 秒数) + duration = item.get("duration") + duration_str = parse_duration(duration) # 返回如 "5s" + try: + normalized["duration"] = int(re.search(r"\d+", duration_str).group()) + except (AttributeError, ValueError): + normalized["duration"] = 5 + + # 计算字数 + normalized["word_count"] = len(normalized["voiceover"]) + + normalized_shots.append(normalized) + + return normalized_shots + + +def _normalize_json_quotes(json_str: str) -> str: + """ + 将中文引号(弯引号)替换为英文引号 + + 某些 AI 模型会在长文本生成中混用中英文标点,导致 JSON 解析失败。 + 此函数将中文引号 " 和 " 替换为标准 JSON 使用的英文引号 "。 + + Args: + json_str: 原始 JSON 字符串 + + Returns: + 规范化后的 JSON 字符串 + """ + # 中文左双引号 " 和右双引号 " 都替换为英文双引号 " + return json_str.replace('"', '"').replace('"', '"') + + +def safe_parse_ai_json_response(content: str) -> tuple[bool, Any, str | None]: + """ + 安全地解析 AI JSON 响应 + + Args: + content: AI 返回的原始内容 + + Returns: + tuple: (是否成功, 解析后的数据, 错误信息) + """ + if not content or not content.strip(): + return False, None, "AI 返回内容为空" + + # 提取 JSON 字符串 + json_str = extract_json_from_markdown(content) + + if not json_str: + logger.error(f"无法从内容中提取 JSON: {content[:200]}...") + return False, None, "无法从 AI 输出中提取 JSON" + + # 尝试直接解析 JSON + try: + data = json.loads(json_str) + return True, data, None + except json.JSONDecodeError: + pass # 解析失败,尝试修复 + + # 尝试修复中文引号问题 + normalized = _normalize_json_quotes(json_str) + + try: + data = json.loads(normalized) + logger.info("JSON 引号规范化成功") + return True, data, None + except json.JSONDecodeError as e: + logger.error(f"JSON 解析失败: {e}") + logger.error(f"原始内容前 500 字符: {json_str[:500]!r}") + return False, None, f"JSON 解析失败: {e}" + except Exception as e: + logger.error(f"解析 AI 响应时发生未知错误: {e}") + return False, None, f"解析错误: {e}" + + +def validate_shots_structure(shots: list[dict]) -> tuple[bool, list[str]]: + """ + 验证分镜列表的结构完整性 + + Args: + shots: 分镜列表 + + Returns: + tuple: (是否有效, 错误信息列表) + """ + errors = [] + + if not shots: + errors.append("分镜列表为空") + return False, errors + + for idx, shot in enumerate(shots): + # 检查必需字段 + if not isinstance(shot, dict): + errors.append(f"分镜 {idx + 1} 不是字典类型") + continue + + # 检查 voiceover(允许为空字符串但不允许缺失) + if "voiceover" not in shot: + errors.append(f"分镜 {idx + 1} 缺少 voiceover 字段") + + # 检查 id + if "id" not in shot: + errors.append(f"分镜 {idx + 1} 缺少 id 字段") + elif not isinstance(shot.get("id"), int): + errors.append(f"分镜 {idx + 1} 的 id 不是整数") + + # 检查 type + if "type" not in shot: + errors.append(f"分镜 {idx + 1} 缺少 type 字段") + + return len(errors) == 0, errors diff --git a/python-api/app/services/anytocopy_service.py b/python-api/app/services/anytocopy_service.py new file mode 100644 index 0000000..2873fdd --- /dev/null +++ b/python-api/app/services/anytocopy_service.py @@ -0,0 +1,350 @@ +""" +AnyToCopy 视频文案提取服务 +============================ + +支持 50+ 平台视频文案提取、视频去水印 +文档: https://www.anytocopy.com/account/api/docs +""" + +import asyncio +import logging +import re + +import aiohttp +from pydantic import BaseModel, Field + +from app.config import get_settings + +logger = logging.getLogger(__name__) + + +class AnyToCopyConfig(BaseModel): + """AnyToCopy 配置""" + + api_key: str = Field(default="", description="API Key") + api_secret: str = Field(default="", description="API Secret") + base_url: str = Field( + default="https://api.anytocopy.com/vip/open-api/v1", description="API Base URL" + ) + + +class VideoExtractResult(BaseModel): + """视频提取结果""" + + task_id: str + title: str = "" + content: str = "" + text_content: str = "" # 语音转文字文案 + video_url: str = "" + audio_url: str = "" + cover: str = "" + platform: str = "" + duration: float = 0.0 + status: str = "" # WAITING, SUCCESS, FAILURE + error_message: str = "" + + +class AnyToCopyService: + """ + AnyToCopy 视频文案提取服务 + + 支持平台: + - 小红书 (xhs) + - 抖音 (douyin) + - 快手 (kuaishou) + - 等 50+ 平台 + """ + + # 支持的视频平台链接正则 + PLATFORM_PATTERNS = { + "xiaohongshu": [ + r"https?://(www\.)?xiaohongshu\.com/.*", + r"https?://xhslink\.com/[a-zA-Z0-9_-]+", + r"https?://(www\.)?xhs\.cn/.*", + ], + "douyin": [ + r"https?://(www\.)?douyin\.com/.*", + r"https?://v\.douyin\.com/[a-zA-Z0-9_-]+", + r"https?://(www\.)?iesdouyin\.com/.*", + ], + "kuaishou": [ + r"https?://(www\.)?kuaishou\.com/.*", + r"https?://v\.kuaishou\.com/[a-zA-Z0-9_-]+", + ], + "bilibili": [ + r"https?://(www\.)?bilibili\.com/.*", + r"https?://b23\.tv/[a-zA-Z0-9_-]+", + ], + "weibo": [ + r"https?://(www\.)?weibo\.com/.*", + r"https?://m\.weibo\.cn/.*", + ], + } + + def __init__(self, config: dict | None = None): + self.config = config or {} + self.api_key = self.config.get("api_key", "") + self.api_secret = self.config.get("api_secret", "") + self.base_url = self.config.get("base_url", "https://api.anytocopy.com/vip/open-api/v1") + + def _get_headers(self) -> dict[str, str]: + """获取请求头""" + return { + "X-API-Key": self.api_key, + "X-API-Secret": self.api_secret, + "Content-Type": "application/json", + } + + @classmethod + def is_video_url(cls, text: str) -> bool: + """ + 检测文本是否为视频链接 + + Args: + text: 输入文本 + + Returns: + bool: 是否为视频链接 + """ + if not text or not isinstance(text, str): + return False + + text = text.strip() + + # 检查是否匹配任一平台链接模式 + for platform, patterns in cls.PLATFORM_PATTERNS.items(): + for pattern in patterns: + if re.match(pattern, text, re.IGNORECASE): + return True + + return False + + @classmethod + def extract_url_from_text(cls, text: str) -> str | None: + """ + 从文本中提取视频链接 + + Args: + text: 可能包含链接的文本 + + Returns: + str | None: 提取的链接或 None + """ + if not text or not isinstance(text, str): + return None + + # URL 正则匹配(排除中文标点和常见标点) + url_pattern = r"https?://[a-zA-Z0-9._~:/?#\[\]@!$&'()*+,;=%-]+" + urls = re.findall(url_pattern, text) + + for url in urls: + # 清理尾部标点 + url = url.rstrip("。,!?;:" "''()【】、") + if cls.is_video_url(url): + return url + + return None + + async def submit_task(self, work_url: str, task_type: str = "TEXT") -> dict: + """ + 提交视频文案提取任务 + + Args: + work_url: 作品链接 + task_type: 任务类型,默认 TEXT + + Returns: + dict: 包含 taskId 或错误信息 + """ + if not self.api_key or not self.api_secret: + return {"code": 500, "msg": "AnyToCopy API Key 未配置"} + + url = f"{self.base_url}/video/extract" + params = {"workUrl": work_url, "taskType": task_type} + + try: + async with aiohttp.ClientSession() as session: + async with session.post(url, headers=self._get_headers(), params=params) as resp: + data = await resp.json() + logger.info(f"AnyToCopy submit_task response: {data}") + return data + except Exception as e: + logger.error(f"AnyToCopy submit_task error: {e}") + return {"code": 500, "msg": f"请求失败: {str(e)}"} + + async def query_task(self, task_id: str) -> dict: + """ + 查询任务状态和结果 + + Args: + task_id: 任务 ID + + Returns: + dict: 任务状态和结果 + """ + url = f"{self.base_url}/video/query" + params = {"taskId": task_id} + + try: + async with aiohttp.ClientSession() as session: + async with session.get(url, headers=self._get_headers(), params=params) as resp: + data = await resp.json() + return data + except Exception as e: + logger.error(f"AnyToCopy query_task error: {e}") + return {"code": 500, "msg": f"请求失败: {str(e)}"} + + async def extract_video_content( + self, + work_url: str, + max_retries: int = 60, + poll_interval: float = 3.0, + ) -> VideoExtractResult: + """ + 完整的视频提取流程(提交 + 轮询) + + Args: + work_url: 作品链接 + max_retries: 最大轮询次数 + poll_interval: 轮询间隔(秒) + + Returns: + VideoExtractResult: 提取结果 + """ + # 1. 提交任务 + submit_result = await self.submit_task(work_url) + if submit_result.get("code") != 200: + return VideoExtractResult( + task_id="", + status="FAILURE", + error_message=submit_result.get("msg", "提交任务失败"), + ) + + task_id = submit_result["data"] + logger.info(f"AnyToCopy task submitted, taskId: {task_id}") + + # 2. 轮询查询 + for i in range(max_retries): + await asyncio.sleep(poll_interval) + + query_result = await self.query_task(task_id) + if query_result.get("code") != 200: + continue + + data = query_result.get("data", {}) + status = data.get("status") + + if status == "SUCCESS": + logger.info(f"AnyToCopy task {task_id} completed successfully") + return VideoExtractResult( + task_id=task_id, + title=data.get("title", ""), + content=data.get("content", ""), + text_content=data.get("textContent", ""), + video_url=data.get("videoUrl", ""), + audio_url=data.get("audioUrl", ""), + cover=data.get("cover", ""), + platform=data.get("platform", ""), + duration=data.get("duration", 0.0), + status="SUCCESS", + error_message=data.get("errorMessage", ""), + ) + elif status in ("FAILURE", "FAILED"): + logger.error(f"AnyToCopy task {task_id} failed: {data.get('errorMessage')}") + return VideoExtractResult( + task_id=task_id, + status="FAILURE", + error_message=data.get("errorMessage", "任务执行失败"), + ) + else: + logger.debug( + f"AnyToCopy task {task_id} status: {status}, retry {i+1}/{max_retries}" + ) + + # 轮询超时 + logger.warning(f"AnyToCopy task {task_id} polling timeout") + return VideoExtractResult( + task_id=task_id, + status="TIMEOUT", + error_message="轮询超时,任务未完成", + ) + + async def extract_text_from_input(self, user_input: str) -> dict: + """ + 智能提取输入中的文案 + + - 如果是视频链接,提取视频文案 + - 如果不是链接,返回原文 + + Args: + user_input: 用户输入(可能是链接或文案) + + Returns: + dict: { + "is_video_url": bool, + "original_input": str, + "extracted_text": str, + "video_info": VideoExtractResult | None, + "error": str | None, + } + """ + result = { + "is_video_url": False, + "original_input": user_input, + "extracted_text": user_input, + "video_info": None, + "error": None, + } + + # 检查是否为视频链接 + url = self.extract_url_from_text(user_input) + if not url: + # 不是链接,直接返回原文 + return result + + # 是视频链接,提取文案 + result["is_video_url"] = True + + if not self.api_key or not self.api_secret: + result["error"] = "AnyToCopy API Key 未配置,无法提取视频文案" + return result + + try: + video_result = await self.extract_video_content(url) + result["video_info"] = video_result + + if video_result.status == "SUCCESS": + # 优先使用语音转文字文案,其次使用正文内容 + extracted_text = ( + video_result.text_content or video_result.content or video_result.title + ) + result["extracted_text"] = extracted_text + logger.info(f"AnyToCopy extracted text length: {len(extracted_text)}") + else: + result["error"] = video_result.error_message or "视频文案提取失败" + + except Exception as e: + logger.error(f"AnyToCopy extract_text_from_input error: {e}") + result["error"] = f"提取失败: {str(e)}" + + return result + + +# 全局单例 +_anytocopy_service: AnyToCopyService | None = None + + +def get_anytocopy_service() -> AnyToCopyService: + """获取 AnyToCopyService 单例""" + global _anytocopy_service + if _anytocopy_service is None: + # 从 Settings 加载配置 + settings = get_settings() + + config = { + "api_key": settings.ANYTOCOPY_API_KEY or "", + "api_secret": settings.ANYTOCOPY_API_SECRET or "", + "base_url": settings.ANYTOCOPY_BASE_URL, + } + _anytocopy_service = AnyToCopyService(config) + return _anytocopy_service diff --git a/python-api/app/services/ass_generator.py b/python-api/app/services/ass_generator.py new file mode 100644 index 0000000..7886704 --- /dev/null +++ b/python-api/app/services/ass_generator.py @@ -0,0 +1,113 @@ +""" +ASS 字幕生成器 +============== + +生成带样式的 ASS 字幕文件,使用抖音美好体 (DouyinSans)。 +""" + +from __future__ import annotations + +from app.schemas.caption import CaptionUtterance + + +def generate_ass( + utterances: list[CaptionUtterance], + video_width: int = 1080, + video_height: int = 1920, + fontname: str = "DouyinSansBold", + fontsize: float = 28.0, + primary_color: str = "&H00FFFFFF", # 白色 + outline_color: str = "&H00000000", # 黑色描边 + back_color: str = "&H80000000", # 半透明背景 + outline: float = 2.0, + shadow: float = 1.0, + margin_v: int = 50, +) -> str: + """ + 生成 ASS 字幕内容 + + Args: + utterances: 字幕时间轴列表 + video_width: 视频宽度 + video_height: 视频高度 + fontname: 字体名称(默认为 DouyinSansBold) + fontsize: 字体大小 + primary_color: 主颜色 (&HAABBGGRR 格式) + outline_color: 描边颜色 + back_color: 背景颜色 + outline: 描边宽度 + shadow: 阴影深度 + margin_v: 垂直边距 + + Returns: + ASS 格式字符串 + """ + # Script Info 部分 + script_info = f"""[Script Info] +Title: Generated by Meijiaka AI Video +ScriptType: v4.00+ +PlayResX: {video_width} +PlayResY: {video_height} +ScaledBorderAndShadow: yes +YCbCr Matrix: None + +""" + + # Styles 部分 + styles = f"""[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: Default,{fontname},{fontsize:.1f},{primary_color},&H000000FF,{outline_color},{back_color},0,0,0,0,100,100,0,0,1,{outline:.1f},{shadow:.1f},2,20,20,{margin_v},1 + +""" + + # Events 部分 + events_header = """[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +""" + + events = [] + for u in utterances: + start = _ms_to_ass_time(u.start_time) + end = _ms_to_ass_time(u.end_time) + # 转义特殊字符 + text = u.text.replace("\\", "\\\\").replace("{", "\\{").replace("}", "\\}") + event = f"Dialogue: 0,{start},{end},Default,,0,0,0,,{text}" + events.append(event) + + return script_info + styles + events_header + "\n".join(events) + + +def _ms_to_ass_time(ms: int) -> str: + """毫秒转换为 ASS 时间格式 H:MM:SS.cc""" + hours = ms // 3600000 + minutes = (ms % 3600000) // 60000 + seconds = (ms % 60000) // 1000 + centiseconds = (ms % 1000) // 10 + return f"{hours}:{minutes:02d}:{seconds:02d}.{centiseconds:02d}" + + +# 预设样式 +def generate_ass_short_video(utterances: list[CaptionUtterance]) -> str: + """生成短视频风格的 ASS 字幕(竖屏 9:16)""" + return generate_ass( + utterances=utterances, + video_width=1080, + video_height=1920, + fontsize=32.0, + outline=2.5, + shadow=2.0, + margin_v=80, + ) + + +def generate_ass_professional(utterances: list[CaptionUtterance]) -> str: + """生成专业风格的 ASS 字幕(横屏 16:9)""" + return generate_ass( + utterances=utterances, + video_width=1920, + video_height=1080, + fontsize=24.0, + outline=1.5, + shadow=1.0, + margin_v=60, + ) diff --git a/python-api/app/services/kling_video_service.py b/python-api/app/services/kling_video_service.py new file mode 100644 index 0000000..2d5d3a2 --- /dev/null +++ b/python-api/app/services/kling_video_service.py @@ -0,0 +1,811 @@ +""" +Kling 视频生成服务 +================== + +提供视频生成功能,调用 Kling AI API: +- 分镜视频生成(omni-video)- 数字人分镜 +- 空镜视频生成(文生图 + 图生视频)- 空镜场景 +- 并发控制、轮询查询、视频下载 + +存储路径:~/Documents/Meijiaka/projects/{project_id}/videos/ +命名规则:scene_{segment_id}.mp4 + +空镜生成新流程: +1. 使用画面描述调用文生图(kling-v3) +2. 生成的图片保存到七牛云 +3. 使用七牛云图片 URL 调用图生视频(kling-v2-6),指定自定义音色 +""" + +import asyncio +import contextlib +import logging +import tempfile +from collections.abc import AsyncIterator +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +import aiohttp + +from app.ai.providers.klingai_provider import KlingAIProvider, KlingPromptBuilder +from app.config import get_settings +from app.core.config_loader import get_config_loader +from app.schemas.enums import JobStatus, SegmentStatus +from app.schemas.segment import Segment +from app.services.qiniu_service import get_qiniu_service + +logger = logging.getLogger(__name__) + + +@dataclass +class VideoGenerationJob: + """视频生成作业""" + + job_id: str + project_id: str + segments: list[Segment] = field(default_factory=list) + status: JobStatus = field(default=JobStatus.PENDING) + progress: int = 0 + created_at: float = field(default_factory=lambda: asyncio.get_event_loop().time()) + updated_at: float = field(default_factory=lambda: asyncio.get_event_loop().time()) + error_message: str | None = None + + +class KlingVideoService: + """Kling 视频生成服务""" + + # 并发控制 + MAX_CONCURRENT = 3 + # 轮询间隔(秒) + POLL_INTERVAL = 5 + # 超时时间(秒)- 10分钟 + TIMEOUT = 600 + + # 视频存储根目录 + BASE_STORAGE_DIR = Path.home() / "Documents" / "Meijiaka" / "projects" + + def __init__(self): + self._provider: KlingAIProvider | None = None + self._semaphore: asyncio.Semaphore | None = None + self._jobs: dict[str, VideoGenerationJob] = {} + + async def _get_provider(self) -> KlingAIProvider: + """获取或初始化 KlingAI Provider + + API Key 从 Settings 读取(符合配置规范) + """ + if self._provider is None: + settings = get_settings() + config_loader = get_config_loader() + platform = config_loader.get_platform("klingai") + + # 从 Settings 读取 AK/SK(符合配置规范:.env → Settings → 服务层) + access_key = settings.KLINGAI_ACCESS_KEY + secret_key = settings.KLINGAI_SECRET_KEY + + if not access_key or not secret_key: + raise ValueError("KlingAI 未配置,请设置 KLINGAI_ACCESS_KEY 和 KLINGAI_SECRET_KEY") + + # 从 YAML 读取 base_url(模型配置) + base_url = platform.base_url if platform else None + + self._provider = KlingAIProvider( + { + "access_key": access_key, + "secret_key": secret_key, + "base_url": base_url or "https://api-beijing.klingai.com", + } + ) + return self._provider + + def _get_semaphore(self) -> asyncio.Semaphore: + """获取并发控制信号量""" + if self._semaphore is None: + self._semaphore = asyncio.Semaphore(self.MAX_CONCURRENT) + return self._semaphore + + def _get_project_video_dir(self, project_id: str) -> Path: + """获取项目视频存储目录""" + video_dir = self.BASE_STORAGE_DIR / project_id / "videos" + video_dir.mkdir(parents=True, exist_ok=True) + return video_dir + + async def _poll_image_task(self, provider: KlingAIProvider, task_id: str) -> str: + """轮询文生图任务状态,直到完成或超时 + + Returns: + 生成的图片 URL + """ + start_time = asyncio.get_event_loop().time() + + while True: + elapsed = asyncio.get_event_loop().time() - start_time + if elapsed > self.TIMEOUT: + raise TimeoutError(f"文生图任务 {task_id} 轮询超时({self.TIMEOUT}秒)") + + result = await provider.get_image_task(task_id) + status = result.get("task_status", "unknown") + logger.debug(f"文生图任务 {task_id} 状态: {status}") + + if status == "succeed": + # 获取图片 URL + images = result.get("task_result", {}).get("images", []) + if not images: + raise Exception("文生图任务成功但未返回图片") + image_url = images[0].get("url") + if not image_url: + raise Exception("文生图任务成功但未获取到图片URL") + return image_url + + if status == "failed": + error_msg = result.get("task_status_msg", "未知错误") + raise Exception(f"文生图任务失败: {error_msg}") + + # 继续轮询 + await asyncio.sleep(self.POLL_INTERVAL) + + async def _download_image(self, image_url: str, local_path: Path) -> Path: + """下载图片到本地 + + Args: + image_url: 图片 URL + local_path: 本地保存路径 + + Returns: + 本地文件路径 + """ + async with aiohttp.ClientSession() as session, session.get(image_url) as resp: + resp.raise_for_status() + local_path.write_bytes(await resp.read()) + + logger.info(f"图片下载完成: {local_path}") + return local_path + + async def _create_omni_video_task(self, segment: Segment, human_id: int) -> dict[str, Any]: + """创建 Omni-Video 任务(分镜/带数字人)""" + provider = await self._get_provider() + + prompt = KlingPromptBuilder.omni_segment(segment.scene, segment.voiceover) + + # omni-video 参数 + result = await provider.generate_video_omni( + prompt=prompt, + model="kling-v3-omni", + mode="pro", + aspect_ratio="9:16", + duration=segment.duration, + sound="on", + multi_shot=False, + element_list=[{"element_id": str(human_id)}], + ) + + return result + + async def _create_empty_shot_task(self, segment: Segment, voice_id: str) -> dict[str, Any]: + """创建空镜视频任务(流程:文生图 → 上传七牛 → 图生视频) + + 流程: + 1. 使用画面描述调用文生图(model=kling-v3) + 2. 等待图片生成完成 + 3. 下载图片到本地临时文件 + 4. 上传图片到七牛云,获取公开 URL + 5. 使用图片 URL + 画面描述调用图生视频(model=kling-v2-6),指定音色 + + 通过 segment.stage 字段向外部报告当前处理阶段: + - "image_generating": 文生图阶段 + - "image2video_queued": 图生视频任务已创建(排队中) + """ + provider = await self._get_provider() + qiniu = get_qiniu_service() + + # ========== Step 1: 文生图 ========== + logger.info(f"[空镜] Step 1: 文生图开始 - {segment.scene}") + segment.stage = "image_generating" + + # 使用画面描述作为 prompt,固定 9:16 比例 + image_result = await provider.generate_image( + prompt=segment.scene, + model="kling-v3", + aspect_ratio="9:16", + ) + + image_task_id = image_result.get("task_id") + if not image_task_id: + raise ValueError(f"文生图创建失败,未返回 task_id: {image_result}") + + logger.info(f"[空镜] 文生图任务创建成功: {image_task_id}") + + # ========== Step 2: 轮询等待图片生成完成 ========== + image_url = await self._poll_image_task(provider, image_task_id) + logger.info(f"[空镜] 文生图完成: {image_url}") + + # ========== Step 3: 下载图片到本地临时文件 ========== + temp_dir = Path(tempfile.gettempdir()) / "meijiaka_empty_shot" + temp_dir.mkdir(parents=True, exist_ok=True) + temp_image_path = temp_dir / f"{image_task_id}.jpg" + + await self._download_image(image_url, temp_image_path) + logger.info(f"[空镜] 图片下载到本地: {temp_image_path}") + + # ========== Step 4: 上传图片到七牛云 ========== + qiniu_result = qiniu.upload_file( + local_path=str(temp_image_path), + file_type="image", + check_duplicate=True, + ) + qiniu_image_url = qiniu_result["url"] + logger.info(f"[空镜] 图片上传到七牛云: {qiniu_image_url}") + + # 清理临时文件 + with contextlib.suppress(Exception): + temp_image_path.unlink() + + # ========== Step 5: 图生视频 ========== + logger.info(f"[空镜] Step 5: 图生视频开始 - {qiniu_image_url}") + # 更新阶段:图片已生成完成,开始排队生成视频 + # 这个 stage 会被 process_job_stream 检测到并推送 SSE + segment.stage = "image2video_queued" + + # 构建提示词:画面描述 + 配音文案 + prompt = KlingPromptBuilder.empty_shot(segment.scene, segment.voiceover) + + # 调用图生视频 API,model=kling-v2-6 + # 添加 voice_list 指定自定义音色,prompt 中使用 <<>> 引用 + # sound=on 确保音画同出 + # negative_prompt 避免画外音断句问题 + result = await provider.generate_video_image2video( + prompt=prompt, + image_url=qiniu_image_url, + model="kling-v2-6", + mode="pro", + duration=segment.duration, + voice_list=[{"voice_id": voice_id}], + sound="on", + negative_prompt="画外音没有标点的时候不要轻易断句", + ) + + return result + + async def _submit_empty_shot_image(self, segment: Segment) -> dict[str, Any]: + """提交空镜文生图任务(仅提交,不轮询) + + Returns: + Kling API 返回结果,包含 image_task_id + """ + provider = await self._get_provider() + result = await provider.generate_image( + prompt=segment.scene, + model="kling-v3", + aspect_ratio="9:16", + ) + return result + + async def _submit_empty_shot_video(self, segment: Segment, image_url: str) -> dict[str, Any]: + """提交空镜图生视频任务(仅提交,不轮询) + + Args: + segment: 空镜任务 + image_url: 七牛云图片 URL + + Returns: + Kling API 返回结果,包含 video_task_id + """ + provider = await self._get_provider() + voice_id = segment.voice_id or get_settings().DEFAULT_EMPTY_SHOT_VOICE_ID + prompt = KlingPromptBuilder.empty_shot(segment.scene, segment.voiceover) + + result = await provider.generate_video_image2video( + prompt=prompt, + image_url=image_url, + model="kling-v2-6", + mode="pro", + duration=segment.duration, + voice_list=[{"voice_id": voice_id}], + sound="on", + negative_prompt="画外音没有标点的时候不要轻易断句", + ) + return result + + async def _poll_task_status( + self, task_id: str, task_type: str = "omni-video", progress_callback: Any = None + ) -> dict[str, Any]: + """轮询查询任务状态 + + Args: + task_id: Kling AI 任务ID + task_type: 任务类型 (omni-video 或 text2video) + progress_callback: 可选的进度回调,接收 (checked_count: int) 参数 + + Returns: + 任务结果字典 + """ + provider = await self._get_provider() + start_time = asyncio.get_event_loop().time() + check_count = 0 + + while True: + elapsed = asyncio.get_event_loop().time() - start_time + if elapsed > self.TIMEOUT: + raise TimeoutError(f"任务 {task_id} 轮询超时({self.TIMEOUT}秒)") + + try: + if task_type == "omni-video": + result = await provider.get_omni_video_task(task_id) + else: + # text2video, image2video 都使用同一个查询入口 + result = await provider.get_video_task(task_id, task_type=task_type) + + status = result.get("task_status", "unknown") + logger.debug(f"任务 {task_id} 状态: {status}") + + if status in ("succeed", "failed"): + return result + + # 继续轮询 + check_count += 1 + # 每轮询一次就调用回调,让外部有机会 yield 并推送 SSE + if progress_callback: + await progress_callback(check_count) + + await asyncio.sleep(self.POLL_INTERVAL) + + except Exception as e: + logger.error(f"轮询任务 {task_id} 状态失败: {e}") + await asyncio.sleep(self.POLL_INTERVAL) + + async def _download_video(self, video_url: str, local_path: Path) -> Path: + """下载视频到本地 + + Args: + video_url: 视频URL + local_path: 本地存储路径 + + Returns: + 本地文件路径 + """ + async with aiohttp.ClientSession() as session, session.get(video_url) as resp: + resp.raise_for_status() + local_path.write_bytes(await resp.read()) + + logger.info(f"视频下载完成: {local_path}") + return local_path + + async def _process_single_segment(self, segment: Segment, job: VideoGenerationJob) -> Segment: + """处理单个分镜(带并发控制)""" + semaphore = self._get_semaphore() + + async with semaphore: + try: + segment.status = SegmentStatus.PROCESSING + logger.info(f"开始处理分镜 {segment.id} (类型: {segment.type})") + + # 根据分镜类型选择生成方式 + if segment.type == "segment" and segment.human_id: + # 分镜:使用 omni-video + result = await self._create_omni_video_task(segment, int(segment.human_id)) + task_type = "omni-video" + else: + # 空镜:流程 - 文生图 → 上传七牛 → 图生视频 + # 空镜需要 voice_id,优先使用 segment 指定的,否则使用配置中的默认音色 + settings = get_settings() + voice_id = segment.voice_id or settings.DEFAULT_EMPTY_SHOT_VOICE_ID + result = await self._create_empty_shot_task(segment, voice_id) + task_type = "image2video" + + # 获取任务ID + task_id = result.get("task_id") + if not task_id: + raise ValueError(f"创建任务失败,未返回 task_id: {result}") + + segment.provider_task_id = task_id + logger.info(f"分镜 {segment.id} 任务创建成功: {task_id}") + + # 更新 stage:任务已提交,进入队列等待 + # 这个 stage 用于前端显示"进入任务队列" + if segment.type == "segment": + segment.stage = "video_queued" + # 等待 1.5 秒让前端能检测到这个 stage + await asyncio.sleep(1.5) + else: + # 空镜已经是 image2video_queued,不需要再设置 + pass + + # 更新 stage:开始视频生成轮询 + segment.stage = "video_processing" + + # 轮询等待任务完成,传入回调让外部可以定期检查状态 + async def _on_poll_check(check_count: int): + # 每轮询一次就短暂 yield,让事件循环可以处理其他任务 + # 包括让 process_job_stream 的 timeout 触发并检查 stage + await asyncio.sleep(0) + + task_result = await self._poll_task_status( + task_id, task_type, progress_callback=_on_poll_check + ) + + # 检查任务结果 + if task_result.get("task_status") != "succeed": + error_msg = task_result.get("task_status_msg", "未知错误") + raise Exception(f"任务执行失败: {error_msg}") + + # 获取视频URL + videos = task_result.get("task_result", {}).get("videos", []) + if not videos: + raise Exception("任务成功但未返回视频") + + video_url = videos[0].get("url") + if not video_url: + raise Exception("未获取到视频URL") + + segment.video_url = video_url + + # 下载视频到本地 + video_dir = self._get_project_video_dir(job.project_id) + local_filename = f"scene_{segment.id}.mp4" + local_path = video_dir / local_filename + + await self._download_video(video_url, local_path) + + segment.local_path = str(local_path) + + # 上传视频到七牛云(用于字幕生成等后续处理) + try: + qiniu = get_qiniu_service() + qiniu_result = qiniu.upload_video(local_path=str(local_path)) + segment.qiniu_url = qiniu_result["url"] + logger.info(f"分镜 {segment.id} 视频已上传七牛云: {segment.qiniu_url}") + except Exception as e: + logger.error(f"分镜 {segment.id} 上传七牛云失败: {e}") + # 上传失败不影响本地保存,但会记录错误 + segment.qiniu_url = None + + segment.status = SegmentStatus.COMPLETED + + logger.info(f"分镜 {segment.id} 处理完成: {local_path}") + + except Exception as e: + logger.error(f"处理分镜 {segment.id} 失败: {e}") + segment.status = SegmentStatus.FAILED + segment.error_message = str(e) + + return segment + + async def create_job( + self, + project_id: str, + human_id: int | None, + segments_data: list[dict[str, Any]], + ) -> VideoGenerationJob: + """创建视频生成作业 + + Args: + project_id: 项目ID + human_id: 数字人主体ID(分镜类型使用) + segments_data: 分镜数据列表 + + Returns: + 视频生成作业 + """ + import uuid + + job_id = f"video_{uuid.uuid4().hex[:16]}" + + # 构建分镜任务列表 + segments = [] + for idx, segment_data in enumerate(segments_data): + segment = Segment( + id=segment_data.get("id", f"segment_{idx}"), + type=segment_data.get("type", "segment"), + scene=segment_data.get("scene", ""), + voiceover=segment_data.get("voiceover", ""), + human_id=str(human_id) if segment_data.get("type") == "segment" else None, + voice_id=segment_data.get("voice_id"), # 空镜可能需要指定音色 + status=SegmentStatus.PENDING, + ) + segments.append(segment) + + job = VideoGenerationJob( + job_id=job_id, + project_id=project_id, + segments=segments, + ) + + self._jobs[job_id] = job + logger.info(f"创建视频生成作业: {job_id}, 项目: {project_id}, 分镜数: {len(segments)}") + + return job + + async def process_job_stream(self, job_id: str) -> AsyncIterator[dict[str, Any]]: + """流式处理视频生成作业(SSE) + + 进度设计: + - 0-10%: 初始化任务 + - 10-80%: 逐个生成分镜(每个分镜按比例分配) + - 80-95%: 完成处理 + - 95-100%: 最终整理 + """ + job = self._jobs.get(job_id) + if not job: + yield { + "type": "error", + "percent": 0, + "message": "作业不存在", + } + return + + total_segments = len(job.segments) + if total_segments == 0: + yield { + "type": "error", + "percent": 0, + "message": "没有需要处理的分镜", + } + return + + # 计算每个分镜的进度权重 + # 新分配:10% 初始化 + 20% 队列 + 60% 处理 + 10% 验证完成 + queue_weight = 20 / total_segments # 20% 分配给队列阶段 + process_weight = 60 / total_segments # 60% 分配给处理阶段 + + # 计算预计时间(分镜3分钟/个,空镜4分钟/个) + def calculate_estimated_seconds(remaining_segments: list) -> int: + total_minutes = 0 + for segment in remaining_segments: + if segment.type == "empty_shot": + total_minutes += 4 # 空镜4分钟 + else: + total_minutes += 3 # 分镜3分钟 + return total_minutes * 60 # 转换为秒 + + # 1. 初始化阶段 (10%) + yield { + "type": "start", + "percent": 5, + "message": f"开始生成视频,共 {total_segments} 个镜头...", + "job_id": job_id, + } + + await asyncio.sleep(0.5) + + yield { + "type": "processing", + "percent": 10, + "message": "任务初始化...", + } + + job.status = JobStatus.RUNNING + + # 2. 并行处理所有分镜 + pending_tasks: set[asyncio.Task] = set() + for segment in job.segments: + task = asyncio.create_task(self._process_single_segment(segment, job)) + pending_tasks.add(task) + + # 等待所有分镜完成,同时报告进度 + completed_count = 0 + failed_count = 0 + # 跟踪每个空镜的 stage 状态,用于推送细粒度进度 + last_reported_stages: dict[str, str] = {} + last_reported_processing_count = 0 + last_reported_queued_count = 0 + last_progress_yield_time = asyncio.get_event_loop().time() + + while pending_tasks: + # 等待任意一个任务完成,或者超时(用于定期检查 stage) + done_tasks: set[asyncio.Task] = set() + + # 使用 timeout 让事件循环有机会处理其他任务并检查 stage + # 即使没有任何任务完成,也能每 2 秒推送一次进度 + with contextlib.suppress(Exception): + done_tasks, pending_tasks = await asyncio.wait( + pending_tasks, + return_when=asyncio.FIRST_COMPLETED, + timeout=2.0, # 2 秒超时,让出控制权 + ) + + # 检查已完成的任务 + for task in done_tasks: + try: + segment = await task + if segment.status == SegmentStatus.COMPLETED: + completed_count += 1 + else: + failed_count += 1 + except Exception as e: + failed_count += 1 + logger.error(f"分镜任务异常: {e}") + + # 计算各阶段数量 + queued_count = sum( + 1 + for s in job.segments + if s.status == SegmentStatus.PROCESSING + and s.stage in ("video_queued", "image2video_queued") + ) + processing_count = sum( + 1 + for s in job.segments + if s.status == SegmentStatus.PROCESSING and s.stage == "video_processing" + ) + + # 计算进度:10% + 队列阶段 + 处理阶段 + # 队列阶段:最多 20%,按在队列中的数量计算 + # 处理阶段:最多 60%,按已完成+失败数量计算 + queue_progress = min(20, int(queued_count * queue_weight)) + process_progress = min(60, int((completed_count + failed_count) * process_weight)) + current_progress = 10 + queue_progress + process_progress + job.progress = min(current_progress, 80) + + # 检查是否需要推送进度更新(stage变化或数量变化) + stage_changed = False + count_changed = ( + processing_count != last_reported_processing_count + or queued_count != last_reported_queued_count + ) + + for segment in job.segments: + if ( + segment.status == SegmentStatus.PROCESSING + and segment.stage + and segment.stage != last_reported_stages.get(segment.id) + ): + last_reported_stages[segment.id] = segment.stage + stage_changed = True + + # 如果 stage 变化或数量变化,推送更新 + if stage_changed or count_changed: + last_reported_processing_count = processing_count + last_reported_queued_count = queued_count + + # 确定当前主要阶段和消息 + remaining_segments = [ + s for s in job.segments if s.status != SegmentStatus.COMPLETED + ] + if queued_count > 0: + yield { + "type": "processing", + "percent": job.progress, + "message": f"进入任务队列({queued_count}/{total_segments})...", + "completed": completed_count, + "failed": failed_count, + "total": total_segments, + "estimatedSeconds": calculate_estimated_seconds(remaining_segments), + } + elif processing_count > 0: + yield { + "type": "processing", + "percent": job.progress, + "message": f"视频生成中({processing_count}/{total_segments})...", + "completed": completed_count, + "failed": failed_count, + "total": total_segments, + "estimatedSeconds": calculate_estimated_seconds(remaining_segments), + } + + # 如果有任务完成,推送整体进度 + if done_tasks: + # 计算剩余镜头的预计时间 + remaining_segments = [ + s for s in job.segments if s.status != SegmentStatus.COMPLETED + ] + yield { + "type": "processing", + "percent": job.progress, + "message": f"已完成 {completed_count}/{total_segments} 个分镜" + + (f",{failed_count} 个失败" if failed_count > 0 else ""), + "completed": completed_count, + "failed": failed_count, + "total": total_segments, + "estimatedSeconds": calculate_estimated_seconds(remaining_segments), + } + last_progress_yield_time = asyncio.get_event_loop().time() + + # 即使没有任务完成,也定期检查 stage(每3秒至少推送一次) + # 这确保了在视频轮询期间,前端仍能收到状态更新 + current_time = asyncio.get_event_loop().time() + if ( + not done_tasks + and not stage_changed + and (current_time - last_progress_yield_time > 3.0) + ): + # 检查是否有分镜正在视频处理阶段,保持前端状态活跃 + has_video_processing = any( + s.status == SegmentStatus.PROCESSING and s.stage == "video_processing" + for s in job.segments + ) + if has_video_processing: + # 计算剩余镜头的预计时间 + remaining_segments = [ + s for s in job.segments if s.status != SegmentStatus.COMPLETED + ] + yield { + "type": "processing", + "percent": job.progress, + "message": "视频生成中...", + "completed": completed_count, + "failed": failed_count, + "total": total_segments, + "estimatedSeconds": calculate_estimated_seconds(remaining_segments), + } + last_progress_yield_time = current_time + # 短暂 sleep 避免 CPU 空转 + await asyncio.sleep(0.5) + + # 3. 完成阶段 (90-100%) + yield { + "type": "processing", + "percent": 90, + "message": "验证文件...", + } + + await asyncio.sleep(0.5) + + if failed_count == 0: + job.status = JobStatus.COMPLETED + elif completed_count == 0: + job.status = JobStatus.FAILED + else: + job.status = JobStatus.FAILED + job.progress = 100 + job.updated_at = asyncio.get_event_loop().time() + + # 构建结果 + results = [] + for segment in job.segments: + results.append( + { + "segment_id": segment.id, + "type": segment.type, + "status": segment.status.value, + "task_id": segment.provider_task_id, + "video_url": segment.video_url, + "local_path": segment.local_path, + "qiniu_url": segment.qiniu_url, # 七牛云 URL(用于字幕生成等后续处理) + "error_message": segment.error_message, + } + ) + + yield { + "type": "complete", + "percent": 100, + "message": f"任务完成。成功生成 {completed_count} 个视频。", + "job_id": job_id, + "results": results, + } + + def get_job(self, job_id: str) -> VideoGenerationJob | None: + """获取作业信息""" + return self._jobs.get(job_id) + + def get_job_status(self, job_id: str) -> dict[str, Any | None] | None: + """获取作业状态""" + job = self._jobs.get(job_id) + if not job: + return None + + return { + "job_id": job.job_id, + "project_id": job.project_id, + "status": job.status.value, + "progress": job.progress, + "total_segments": len(job.segments), + "completed_segments": sum( + 1 for s in job.segments if s.status == SegmentStatus.COMPLETED + ), + "failed_segments": sum(1 for s in job.segments if s.status == SegmentStatus.FAILED), + "created_at": job.created_at, + "updated_at": job.updated_at, + "error_message": job.error_message, + } + + +# 全局单例 +_kling_video_service: KlingVideoService | None = None + + +def get_kling_video_service() -> KlingVideoService: + """获取 KlingVideoService 单例""" + global _kling_video_service + if _kling_video_service is None: + _kling_video_service = KlingVideoService() + return _kling_video_service diff --git a/python-api/app/services/qiniu_service.py b/python-api/app/services/qiniu_service.py new file mode 100644 index 0000000..152c42c --- /dev/null +++ b/python-api/app/services/qiniu_service.py @@ -0,0 +1,486 @@ +""" +七牛云对象存储服务 +==================== + +提供音频、视频文件的上传、管理和访问功能。 + +使用场景: +1. 声音克隆 - 上传音频样本文件 +2. 音频生成 - 存储 TTS 生成的音频 +3. 视频素材 - 上传视频文件用于后续处理 +""" + +import mimetypes +import uuid +from datetime import datetime +from pathlib import Path +from typing import BinaryIO + +from qiniu import Auth, BucketManager, CdnManager, put_file, put_stream + +from app.config import get_settings + + +class QiniuService: + """ + 七牛云服务封装 + + 封装了常用的文件上传、下载、管理操作, + 专为美家卡智影项目的音视频文件处理场景设计。 + """ + + # 文件类型目录映射 + TYPE_DIRECTORIES = { + "audio": "audios", + "video": "videos", + "image": "images", + "avatar": "avatars", # 形象克隆视频 + } + + # 允许的文件类型 + ALLOWED_AUDIO_TYPES = { + "audio/mpeg", + "audio/mp3", + "audio/wav", + "audio/x-m4a", + "audio/aac", + "audio/ogg", + } + ALLOWED_VIDEO_TYPES = {"video/mp4", "video/quicktime", "video/x-msvideo", "video/webm"} + ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/gif", "image/webp"} + + def __init__(self): + """ + 初始化七牛云服务 + + 支持多 bucket: + - 图片: img-liche / img.liche.cn + - 视频/音频: media-liche / media.liche.cn + """ + settings = get_settings() + self.access_key = settings.QINIU_ACCESS_KEY + self.secret_key = settings.QINIU_SECRET_KEY + + # 图片 bucket 配置 + self.image_bucket = settings.QINIU_IMAGE_BUCKET + self.image_domain = settings.QINIU_IMAGE_DOMAIN + + # 视频/音频 bucket 配置 + self.video_bucket = settings.QINIU_VIDEO_BUCKET + self.video_domain = settings.QINIU_VIDEO_DOMAIN + + if not all([self.access_key, self.secret_key]): + raise ValueError( + "七牛云配置不完整,请设置环境变量: " "QINIU_ACCESS_KEY, QINIU_SECRET_KEY" + ) + + # 初始化认证和管理器 + self.auth = Auth(self.access_key, self.secret_key) + self.bucket = BucketManager(self.auth) + self.cdn = CdnManager(self.auth) + + def _get_bucket_and_domain(self, file_type: str) -> tuple[str, str]: + """ + 根据文件类型获取对应的 bucket 和 domain + + Args: + file_type: 文件类型 (audio/video/image/avatar) + + Returns: + (bucket_name, domain) + """ + if file_type == "image": + return self.image_bucket, self.image_domain + # video, avatar, audio 都用视频 bucket + return self.video_bucket, self.video_domain + + # 项目前缀 + PROJECT_PREFIX = "meijiaka" + + def generate_key(self, file_type: str, original_filename: str, user_id: str = None) -> str: + """ + 生成规范的文件存储路径 + + 格式: meijiaka/{type}/{date}/{uuid}.{ext} + + Args: + file_type: 文件类型 (audio/video/image/voice_clone/tts_output) + original_filename: 原始文件名 + user_id: 用户ID(可选,用于目录隔离) + + Returns: + 文件存储 Key + """ + # 获取文件扩展名 + ext = Path(original_filename).suffix.lower() + if not ext: + # 根据 file_type 设置默认扩展名 + ext_map = {"audio": ".mp3", "video": ".mp4", "image": ".jpg"} + ext = ext_map.get(file_type, ".bin") + + # 生成唯一标识 + unique_id = str(uuid.uuid4())[:12] + + # 获取类型目录 + type_dir = self.TYPE_DIRECTORIES.get(file_type, "others") + + # 构建路径(带项目前缀) + date_str = datetime.now().strftime("%Y%m") + + if user_id: + return f"{self.PROJECT_PREFIX}/{type_dir}/{user_id}/{date_str}/{unique_id}{ext}" + return f"{self.PROJECT_PREFIX}/{type_dir}/{date_str}/{unique_id}{ext}" + + def validate_file_type(self, mime_type: str, allowed_types: set) -> bool: + """验证文件 MIME 类型是否在允许列表中""" + return mime_type in allowed_types + + def get_upload_token( + self, bucket: str, key: str, expires: int = 3600, policy: dict = None + ) -> str: + """ + 生成上传凭证(客户端直传使用) + + Args: + bucket: 存储空间名称 + key: 文件存储 Key + expires: Token 有效期(秒),默认 1 小时 + policy: 自定义上传策略(可选) + + Returns: + 上传 Token 字符串 + """ + return self.auth.upload_token(bucket, key, expires, policy) + + def _calculate_file_hash(self, local_path: Path) -> str: + """计算文件的 MD5 哈希""" + import hashlib + + md5 = hashlib.md5() + with open(local_path, "rb") as f: + for chunk in iter(lambda: f.read(8192), b""): + md5.update(chunk) + return md5.hexdigest() + + def _find_file_by_hash(self, bucket: str, file_hash: str) -> dict | None: + """ + 根据文件哈希查找已存在的文件 + + Args: + bucket: 存储空间名称 + file_hash: 文件 MD5 哈希 + + Returns: + 文件信息或 None + """ + # 列举最近上传的 1000 个文件进行比对 + ret, eof, info = self.bucket.list(bucket, prefix=f"{self.PROJECT_PREFIX}/", limit=1000) + + if ret and "items" in ret: + for item in ret["items"]: + if item.get("hash") == file_hash: + return { + "key": item["key"], + "hash": item["hash"], + "fsize": item["fsize"], + "mime_type": item.get("mimeType", "application/octet-stream"), + } + return None + + def upload_file( + self, + local_path: str, + key: str = None, + file_type: str = "audio", + user_id: str = None, + check_duplicate: bool = True, + ) -> dict: + """ + 上传本地文件到七牛云 + + Args: + local_path: 本地文件路径 + key: 指定存储 Key(可选,不指定则自动生成) + file_type: 文件类型,用于自动生成 Key 和选择 bucket + user_id: 用户ID(可选) + check_duplicate: 是否检查重复文件(默认开启) + + Returns: + { + "key": 文件Key, + "hash": 文件哈希, + "url": 访问URL, + "mime_type": MIME类型, + "fsize": 文件大小(字节), + "is_duplicate": 是否复用已有文件 + } + """ + local_path = Path(local_path) + if not local_path.exists(): + raise FileNotFoundError(f"文件不存在: {local_path}") + + # 根据文件类型获取对应的 bucket 和 domain + bucket, domain = self._get_bucket_and_domain(file_type) + + # 计算文件 MD5 哈希 + file_md5 = self._calculate_file_hash(local_path) + + # 检查是否已存在相同文件 + if check_duplicate: + existing = self._find_file_by_hash(bucket, file_md5) + if existing: + return { + "key": existing["key"], + "hash": existing["hash"], + "url": self.get_file_url(domain, existing["key"]), + "mimeType": existing.get("mime_type", "application/octet-stream"), + "fsize": existing["fsize"], + "isDuplicate": True, + "message": "文件已存在,直接复用", + } + + # 自动生成 Key + if key is None: + key = self.generate_key(file_type, local_path.name, user_id) + + # 生成上传 Token + token = self.get_upload_token(bucket, key) + + # 使用分片上传 + ret, info = put_file(up_token=token, key=key, file_path=str(local_path)) + + if ret is None: + raise Exception(f"上传失败: {info}") + + # 获取文件信息 + mime_type, _ = mimetypes.guess_type(str(local_path)) + fsize = local_path.stat().st_size + + return { + "key": ret["key"], + "hash": ret["hash"], + "url": self.get_file_url(domain, key), + "mimeType": mime_type or "application/octet-stream", + "fsize": fsize, + "isDuplicate": False, + } + + def upload_stream( + self, stream: BinaryIO, key: str, mime_type: str = "application/octet-stream" + ) -> dict: + """ + 上传文件流到七牛云 + + Args: + stream: 文件流对象 + key: 文件存储 Key + mime_type: 文件 MIME 类型 + + Returns: + 上传结果字典 + """ + token = self.get_upload_token(key) + + ret, info = put_stream( + up_token=token, key=key, data_stream=stream, params=None, mime_type=mime_type + ) + + if ret is None: + raise Exception(f"上传失败: {info}") + + return {"key": ret["key"], "hash": ret["hash"], "url": self.get_file_url(key)} + + def upload_audio(self, local_path: str, user_id: str = None, key: str = None) -> dict: + """ + 上传音频文件(专用接口) + + Args: + local_path: 本地音频文件路径 + user_id: 用户ID(可选) + key: 指定 Key(可选) + + Returns: + 上传结果 + """ + # 验证文件类型 + mime_type, _ = mimetypes.guess_type(local_path) + if mime_type and not self.validate_file_type(mime_type, self.ALLOWED_AUDIO_TYPES): + raise ValueError(f"不支持的音频格式: {mime_type}") + + return self.upload_file(local_path=local_path, key=key, file_type="audio", user_id=user_id) + + def upload_video(self, local_path: str, user_id: str = None, key: str = None) -> dict: + """ + 上传视频文件(专用接口) + + Args: + local_path: 本地视频文件路径 + user_id: 用户ID(可选) + key: 指定 Key(可选) + + Returns: + 上传结果 + """ + # 验证文件类型 + mime_type, _ = mimetypes.guess_type(local_path) + if mime_type and not self.validate_file_type(mime_type, self.ALLOWED_VIDEO_TYPES): + raise ValueError(f"不支持的视频格式: {mime_type}") + + return self.upload_file(local_path=local_path, key=key, file_type="video", user_id=user_id) + + def upload_avatar_video( + self, + local_path: str, + user_id: str = None, + key: str = None, + file_hash: str = None, + ) -> dict: + """ + 上传形象克隆视频(专用接口) + + Args: + local_path: 本地视频文件路径 + user_id: 用户ID(可选) + key: 指定 Key(可选) + file_hash: 前端计算的文件SHA256哈希(可选,用于重复检测) + + Returns: + 上传结果,包含 isDuplicate(如果检测到七牛云上已有相同文件) + """ + # 验证文件类型 + mime_type, _ = mimetypes.guess_type(local_path) + if mime_type and not self.validate_file_type(mime_type, self.ALLOWED_VIDEO_TYPES): + raise ValueError(f"不支持的视频格式: {mime_type}") + + # 计算本地文件哈希用于七牛云重复检测 + local_file_hash = self._calculate_file_hash(Path(local_path)) + + # 检查七牛云上是否已有相同文件 + existing = self._find_file_by_hash(self.video_bucket, local_file_hash) + if existing: + logger.info(f"File already exists in Qiniu: {existing['key']}") + return { + "key": existing["key"], + "hash": existing["hash"], + "url": self.get_file_url(existing["key"]), + "mimeType": existing.get("mime_type", "video/mp4"), + "fsize": existing["fsize"], + "isDuplicate": True, + "message": "文件已存在,直接复用", + "existingTaskId": None, + } + + # 上传文件 + result = self.upload_file( + local_path=local_path, + key=key, + file_type="avatar", + user_id=user_id, + check_duplicate=False, # 已经在上面检查过了 + ) + + # 确保返回所有必需字段 + result["existingTaskId"] = None + return result + + def get_file_url(self, domain: str, key: str, expires: int = 0) -> str: + """ + 获取文件访问 URL + + Args: + domain: 加速域名 + key: 文件 Key + expires: 过期时间(秒),0 表示永久(公有空间) + + Returns: + 文件访问 URL + """ + base_url = f"https://{domain}/{key}" + + if expires > 0: + # 生成私有链接(临时 URL) + return self.auth.private_download_url(base_url, expires) + + return base_url + + def delete_file(self, bucket: str, key: str) -> bool: + """ + 删除文件 + + Args: + bucket: 存储空间名称 + key: 文件 Key + + Returns: + 是否删除成功 + """ + ret, info = self.bucket.delete(bucket, key) + return ret == {} + + def get_file_info(self, bucket: str, key: str) -> dict | None: + """ + 获取文件元信息 + + Args: + bucket: 存储空间名称 + key: 文件 Key + + Returns: + 文件信息字典,文件不存在返回 None + """ + ret, info = self.bucket.stat(bucket, key) + if ret is None: + return None + + # 根据 key 前缀推断文件类型,获取对应的 domain + file_type = "video" # 默认 + if "/images/" in key: + file_type = "image" + _, domain = self._get_bucket_and_domain(file_type) + + return { + "key": key, + "fsize": ret.get("fsize"), + "hash": ret.get("hash"), + "mime_type": ret.get("mimeType"), + "put_time": ret.get("putTime"), + "type": ret.get("type"), + "url": self.get_file_url(domain, key), + } + + def refresh_cdn(self, keys: list[str]) -> dict: + """ + 刷新 CDN 缓存 + + Args: + keys: 文件 Key 列表 + + Returns: + 刷新结果 + """ + urls = [] + for key in keys: + # 根据 key 推断文件类型获取 domain + file_type = "video" + if "/images/" in key: + file_type = "image" + _, domain = self._get_bucket_and_domain(file_type) + urls.append(self.get_file_url(domain, key)) + + ret, info = self.cdn.refresh_urls(urls) + return { + "code": ret.get("code"), + "request_id": info.req_id if hasattr(info, "req_id") else None, + } + + +# 全局单例 +_qiniu_service: QiniuService | None = None + + +def get_qiniu_service() -> QiniuService: + """获取 QiniuService 单例""" + global _qiniu_service + if _qiniu_service is None: + _qiniu_service = QiniuService() + return _qiniu_service diff --git a/python-api/app/services/script_service.py b/python-api/app/services/script_service.py new file mode 100644 index 0000000..758cc05 --- /dev/null +++ b/python-api/app/services/script_service.py @@ -0,0 +1,662 @@ +""" +脚本生成服务 +============ +""" + +import asyncio +import logging +import math +import re +import time +from collections.abc import AsyncIterator +from pathlib import Path + +from app.ai.model_router import get_model_router +from app.ai.prompts import load_script_system, load_script_user +from app.schemas.script import ScriptGenerationEvent, ScriptShot +from app.services.ai_response_utils import ( + safe_parse_ai_json_response, + validate_and_normalize_shots, +) +from app.services.anytocopy_service import ( + AnyToCopyService, + get_anytocopy_service, +) + +logger = logging.getLogger(__name__) + + +class ScriptService: + """脚本生成服务""" + + # 根据视频时长估算输出字符数(经验值) + # 格式: {时长: (最小字符数, 最大字符数)} + DURATION_ESTIMATES = { + 30: (800, 1200), + 45: (1200, 1600), + 60: (1500, 2000), + 90: (2000, 2800), + } + + def __init__(self): + self.prompts_dir = Path(__file__).parent.parent / "ai" / "prompts" + + def _estimate_total_chars(self, duration: int) -> int: + """ + 根据时长估算总输出字符数 + + Args: + duration: 视频时长(秒) + + Returns: + 预估字符数 + """ + # 找到最接近的预设 + closest_duration = min(self.DURATION_ESTIMATES.keys(), key=lambda x: abs(x - duration)) + min_chars, max_chars = self.DURATION_ESTIMATES[closest_duration] + + # 根据实际时长在区间内插值 + ratio = duration / closest_duration + estimated = int(min_chars + (max_chars - min_chars) * ratio) + + logger.debug(f"时长 {duration}s 预估字符数: {estimated}") + return estimated + + def _calculate_progress( + self, + current_chars: int, + estimated_total: int, + elapsed_time: float, + min_expected_time: float = 5.0, + ) -> int: + """ + 计算平滑进度(使用对数曲线)- 优化版,避免抖动 + + 设计思路: + - 主要基于内容生成进度,时间只作为保底 + - 使用单调递增函数,确保进度只增不减 + - 前 20% 内容:进度到 30%(慢启动) + - 中间 60% 内容:进度到 75%(稳定生成期) + - 最后 20% 内容:进度到 85%(收尾阶段) + + Args: + current_chars: 当前字符数 + estimated_total: 预估总字符数 + elapsed_time: 已过去时间(秒) + min_expected_time: 最少预期的生成时间(避免太快跑完) + + Returns: + 进度百分比 (0-85) + """ + # 基于内容的进度(对数曲线) + ratio = min(current_chars / estimated_total, 1.5) # 允许超出生成 50% + + # 对数曲线:前期慢,后期快 + if ratio <= 1.0: + # 未完成或刚好完成:使用调整后的对数曲线,最高到 85% + progress_ratio = math.log(1 + ratio * 2) / math.log(3) * 0.85 + else: + # 已超出生成:从 85% 线性增长,最多到 95%(预留空间给后续阶段) + progress_ratio = 0.85 + min((ratio - 1) * 0.2, 0.1) + + # 基于时间的保底进度(只在内容很少时生效,避免生成太快时进度条没动) + # 使用平滑的保底函数,只在前期(前3秒)和内容很少时生效 + time_progress = 0 + if current_chars < estimated_total * 0.3 and elapsed_time < min_expected_time: + # 内容生成少于30%且时间少于5秒时,提供保底进度 + time_progress = min(elapsed_time / min_expected_time * 0.15, 0.15) + + # 取较大值,但主要依赖 progress_ratio,time_progress 只作为早期保底 + final_ratio = max(progress_ratio, time_progress) + + # 生成阶段最高到 85% + return min(int(final_ratio * 100), 85) + + def _load_prompt(self, name: str) -> str: + """加载 Prompt 模板""" + prompt_file = self.prompts_dir / f"{name}.txt" + if prompt_file.exists(): + return prompt_file.read_text(encoding="utf-8") + return "" + + @staticmethod + def _extract_json(content: str) -> str: + """ + 从 Markdown 代码块中提取 JSON,或返回原始内容 + + 支持格式: + - ```json {...} ``` + - ``` {...} ``` + - 纯 JSON 文本 + """ + if not content: + return "" + + content = content.strip() + + # 匹配 ```json ... ``` 或 ``` ... ``` + pattern = r"```(?:json)?\s*([\s\S]*?)\s*```" + matches = re.findall(pattern, content) + + if matches: + # 取最后一个匹配(避免前面有示例代码) + return matches[-1].strip() + + # 如果没有代码块,返回原始内容 + return content + + async def generate_script( + self, + topic: str, + duration: int, + script_type: str, + model: str | None = None, + ) -> list[ScriptShot]: + """ + 同步生成脚本 + + Args: + topic: 创作主题(支持视频链接,自动提取文案) + duration: 视频时长(秒) + script_type: 脚本类型 + model: 指定模型 + + Returns: + 分镜列表 + """ + # 1. 检测并提取视频链接中的文案 + anytocopy = get_anytocopy_service() + extract_result = await anytocopy.extract_text_from_input(topic) + + if extract_result["error"]: + logger.warning(f"视频文案提取失败: {extract_result['error']}") + # 提取失败但不中断,使用原始输入 + + if extract_result["is_video_url"]: + logger.info(f"检测到视频链接,提取文案长度: {len(extract_result['extracted_text'])}") + # 使用提取的文案作为创作主题 + topic = extract_result["extracted_text"] or topic + + # 2. 获取 model_router + model_router = await get_model_router() + + # 加载 Prompt(使用新的 loader) + system_prompt = load_script_system() + user_prompt = load_script_user( + topic=topic, + duration=duration, + script_type=script_type, + ) + + logger.info(f"同步生成脚本: topic={topic[:20]}, duration={duration}") + + # 调用 AI 生成 + result = await model_router.generate( + prompt=user_prompt, + system_prompt=system_prompt, + model_id=model, + task_type="script", + temperature=0.7, + ) + + # 检查返回内容 + if not result.content or not result.content.strip(): + logger.error("AI 返回内容为空") + raise ValueError("AI 返回内容为空,请检查模型配置或重试") + + logger.info(f"AI 返回内容长度: {len(result.content)} 字符") + + # 使用安全的 JSON 解析 + success, parsed_data, error_msg = safe_parse_ai_json_response(result.content) + + if not success: + logger.error(f"JSON 解析失败: {error_msg}") + logger.error(f"原始内容: {result.content[:500]!r}") + raise ValueError(error_msg or "AI 返回格式错误,无法解析为 JSON") + + # 验证并标准化分镜数据 + try: + shots_data = validate_and_normalize_shots(parsed_data) + + if not shots_data: + raise ValueError("AI 返回的分镜数据为空或格式不正确") + + # 转换为 ScriptShot 对象 + shots = [ScriptShot(**shot) for shot in shots_data] + logger.info(f"成功解析 {len(shots)} 个分镜") + return shots + + except Exception as e: + logger.error(f"分镜数据标准化失败: {e}") + raise ValueError(f"分镜数据处理失败: {str(e)}") + + async def generate_script_stream( + self, + topic: str, + duration: int, + script_type: str, + model: str | None = None, + ) -> AsyncIterator[ScriptGenerationEvent]: + """ + 流式生成脚本(SSE)- 优化版 + + 支持视频链接自动提取文案。 + + 进度设计: + - 0-5%: start(初始化) + - 5-15%: analyzing(分析主题,含视频文案提取) + - 15-85%: generating(AI 生成,平滑对数曲线增长) + - 85-92%: validating(JSON 验证) + - 92-98%: parsing(解析分镜) + - 98-100%: complete(完成) + """ + model_router = await get_model_router() + start_time = time.time() + + # 1. 检测并提取视频链接中的文案 + original_topic = topic + anytocopy = get_anytocopy_service() + extracted_info = None # 保存提取的视频信息 + + # 检查是否为视频链接 + if AnyToCopyService.is_video_url(topic) or AnyToCopyService.extract_url_from_text(topic): + yield ScriptGenerationEvent( + type="analyzing", + progress=5, + message="检测到视频链接,正在提取文案...", + ) + + extract_result = await anytocopy.extract_text_from_input(topic) + + if extract_result["error"]: + logger.warning(f"视频文案提取失败: {extract_result['error']}") + yield ScriptGenerationEvent( + type="analyzing", + progress=8, + message="视频文案提取失败,使用原始输入继续生成...", + ) + elif extract_result["is_video_url"]: + extracted_text = extract_result["extracted_text"] + logger.info(f"视频文案提取成功,长度: {len(extracted_text)}") + topic = extracted_text or topic + + # 保存提取的视频信息(只要有 video_info 就返回) + video_info = extract_result.get("video_info") + if video_info: + extracted_info = { + "title": video_info.title, + "content": video_info.content, + "text_content": video_info.text_content, + "platform": video_info.platform, + "duration": video_info.duration, + "original_url": original_topic, + } + + yield ScriptGenerationEvent( + type="analyzing", + progress=10, + message=f"视频文案提取成功,共 {len(extracted_text)} 字符", + ) + + try: + # 加载 Prompt + system_prompt = load_script_system() + user_prompt = load_script_user( + topic=topic, + duration=duration, + script_type=script_type, + ) + + # 1. 开始阶段(0-5%) + yield ScriptGenerationEvent( + type="start", + progress=2, + message="准备生成脚本...", + ) + + # 2. 分析阶段(5-15%) + yield ScriptGenerationEvent( + type="analyzing", + progress=10, + message="分析创作要点", + ) + + # 估算总长度(根据时长) + estimated_total = self._estimate_total_chars(duration) + + # 3. 生成阶段(15-55%)- 降低占比,给后续步骤留更多空间 + yield ScriptGenerationEvent( + type="generating", + progress=15, + message="正在创作脚本...", + ) + + full_content = "" + last_progress = 15 + last_update_time = start_time + update_interval = 0.5 # 最少 500ms 更新一次 + chunk_count = 0 + + logger.info(f"开始流式生成: topic={topic[:20]}, duration={duration}") + + async for chunk in model_router.generate_stream_with_progress( + prompt=user_prompt, + system_prompt=system_prompt, + model_id=model, + task_type="script", + temperature=0.7, + ): + chunk_count += 1 + + if chunk["type"] == "chunk": + chunk_content = chunk.get("content", "") + if not chunk_content: + logger.warning(f"收到空 chunk,序号: {chunk_count}") + continue + + full_content += chunk_content + current_chars = len(full_content) + elapsed = time.time() - start_time + + # 计算平滑进度(对数曲线,最高到55%) + base_progress = self._calculate_progress( + current_chars=current_chars, + estimated_total=estimated_total, + elapsed_time=elapsed, + ) + # 将原来的 15-85 映射到 15-55 + progress = 15 + int((base_progress - 15) * 40 / 70) + + # 限制更新频率,但确保每次有变化都上报(最小 2% 变化) + current_time = time.time() + if progress > last_progress and ( + progress - last_progress >= 2 + or current_time - last_update_time >= update_interval + ): + + yield ScriptGenerationEvent( + type="generating", + progress=progress, + message="正在创作脚本...", + ) + last_progress = progress + last_update_time = current_time + + elif chunk["type"] == "usage": + prompt_tokens = chunk.get("prompt_tokens", 0) + completion_tokens = chunk.get("completion_tokens", 0) + logger.info( + f"Token 使用: prompt={prompt_tokens}, completion={completion_tokens}" + ) + + logger.info(f"流式生成结束: 共 {chunk_count} 个 chunk, {len(full_content)} 字符") + + # 4. 验证阶段(55-70%) + actual_chars = len(full_content) + logger.info(f"生成完成: {actual_chars} 字符 (预估: {estimated_total})") + + yield ScriptGenerationEvent( + type="validating", + progress=60, + message="验证脚本格式...", + ) + + await asyncio.sleep(0.5) + + yield ScriptGenerationEvent( + type="validating", + progress=65, + message="检查数据完整性...", + ) + + await asyncio.sleep(0.5) + + yield ScriptGenerationEvent( + type="validating", + progress=70, + message="验证通过", + ) + + await asyncio.sleep(0.5) + + # 5. 解析阶段(70-80%) + yield ScriptGenerationEvent( + type="parsing", + progress=75, + message="解析分镜内容...", + ) + + # 检查内容是否为空 + if not full_content or not full_content.strip(): + logger.error("AI 返回内容为空") + yield ScriptGenerationEvent( + type="error", + progress=0, + message="AI 返回内容为空,请检查模型配置或重试", + ) + return + + # 记录原始内容(调试用) + logger.info(f"AI 原始输出: {full_content[:500]}...") + + # 使用安全的 JSON 解析 + success, parsed_data, error_msg = safe_parse_ai_json_response(full_content) + + if not success: + logger.error(f"JSON 解析失败: {error_msg}") + logger.error(f"原始内容前500字符: {full_content[:500]!r}") + + # 给前端更详细的错误信息 + error_detail = error_msg or "无法解析 AI 返回的内容" + if not full_content or not full_content.strip(): + error_detail = "AI 返回内容为空,请检查模型配置或重试" + + yield ScriptGenerationEvent( + type="error", + progress=0, + message=f"脚本解析失败: {error_detail}", + ) + return + + # 验证并标准化分镜数据 + try: + shots_data = validate_and_normalize_shots(parsed_data) + + if not shots_data: + logger.error("标准化后分镜列表为空") + yield ScriptGenerationEvent( + type="error", + progress=0, + message="AI 返回的分镜数据为空或格式不正确", + ) + return + + # 转换为 ScriptShot 对象 + shots = [ScriptShot(**shot) for shot in shots_data] + + # 6. 完成阶段(80-100%)- 细分为多个步骤,让用户感知进度 + yield ScriptGenerationEvent( + type="finalizing", + progress=80, + message=f"整理 {len(shots)} 个分镜...", + ) + + await asyncio.sleep(0.5) + + yield ScriptGenerationEvent( + type="finalizing", + progress=85, + message="优化镜头顺序...", + ) + + await asyncio.sleep(0.5) + + yield ScriptGenerationEvent( + type="finalizing", + progress=90, + message="检查时长分配...", + ) + + await asyncio.sleep(0.5) + + yield ScriptGenerationEvent( + type="finalizing", + progress=95, + message="准备完成...", + ) + + await asyncio.sleep(0.5) + + yield ScriptGenerationEvent( + type="complete", + progress=100, + message=f"成功生成 {len(shots)} 个分镜", + result=shots, + extracted_info=extracted_info, + ) + + except Exception as e: + logger.error(f"分镜数据标准化失败: {e}") + yield ScriptGenerationEvent( + type="error", + progress=0, + message=f"分镜数据处理失败: {str(e)}", + ) + + except Exception as e: + logger.exception("脚本生成失败") + yield ScriptGenerationEvent( + type="error", + progress=0, + message=f"生成失败: {str(e)}", + ) + + async def polish_content( + self, + content: str, + polish_type: str = "voiceover", + shot_type: str = "segment", + ) -> str: + """ + 润色内容 + + Args: + content: 待润色内容 + polish_type: 润色类型,可选 "scene"(画面描述)或 "voiceover"(配音文案) + shot_type: 镜头类型,可选 "segment"(分镜)或 "empty_shot"(空镜),仅用于画面润色 + + Returns: + 润色后的内容 + """ + # 获取 model_router + model_router = await get_model_router() + + # 从文件加载提示词模板 + if polish_type == "scene": + # 画面润色需要根据镜头类型选择不同提示词 + if shot_type == "empty_shot": + prompt_template = self._load_prompt("polish/scene_empty_shot") + else: + prompt_template = self._load_prompt("polish/scene_segment") + + # 如果特定类型的提示词不存在,回退到通用 scene 提示词 + if not prompt_template: + prompt_template = self._load_prompt("polish/scene") + else: + # 配音文案润色 + prompt_template = self._load_prompt("polish/voiceover") + + if not prompt_template: + # 最终回退 + prompt_template = "请润色以下内容:\n\n{content}" + + prompt = prompt_template.format(content=content) + + result = await model_router.generate( + prompt=prompt, + task_type="polish", + temperature=0.5, + max_tokens=300, + ) + + return result.content.strip() + + async def check_model_health(self) -> dict: + """检查模型健康状态""" + model_router = await get_model_router() + health_results = await model_router.health_check() + + models = [] + available_count = 0 + recommended = None + + for provider_id, health in health_results.items(): + model_info = { + "id": health.id, + "name": health.name, + "is_available": health.is_available, + "response_time": health.response_time, + "last_error": health.last_error, + } + models.append(model_info) + + if health.is_available: + available_count += 1 + if recommended is None or health.response_time < recommended.get( + "response_time", float("inf") + ): + recommended = model_info + + total = len(models) + + return { + "status": "healthy" if available_count > 0 else "unhealthy", + "models": models, + "recommended_model": recommended, + "total_models": total, + "available_models": available_count, + } + + async def test_model(self, model_id: str | None = None) -> dict: + """测试指定模型连接""" + model_router = await get_model_router() + + import time + + start_time = time.time() + + try: + result = await model_router.generate( + prompt="你好", + model_id=model_id, + max_tokens=5, + ) + + response_time = (time.time() - start_time) * 1000 + + return { + "success": True, + "model": result.model, + "response_time": round(response_time, 2), + "checked_at": time.strftime("%Y-%m-%dT%H:%M:%S"), + } + + except Exception as e: + return { + "success": False, + "model": model_id or "default", + "error": str(e), + "checked_at": time.strftime("%Y-%m-%dT%H:%M:%S"), + } + + +# 全局单例 +_script_service: ScriptService | None = None + + +def get_script_service() -> ScriptService: + """获取 ScriptService 单例""" + global _script_service + if _script_service is None: + _script_service = ScriptService() + return _script_service diff --git a/python-api/app/services/volcengine_caption_service.py b/python-api/app/services/volcengine_caption_service.py new file mode 100644 index 0000000..44af413 --- /dev/null +++ b/python-api/app/services/volcengine_caption_service.py @@ -0,0 +1,566 @@ +""" +火山引擎音视频字幕服务 +====================== + +基于火山引擎 OpenSpeech API 的音视频字幕生成服务。 + +文档: https://www.volcengine.com/docs/6561/80907 +""" + +from __future__ import annotations + +import asyncio +import json +import logging + +import httpx + +from app.config import get_settings +from app.schemas.caption import ( + AutoAlignResult, + CaptionResult, + CaptionUtterance, + CaptionWord, +) + +logger = logging.getLogger(__name__) + + +class VolcengineCaptionError(Exception): + """火山引擎字幕服务异常""" + + def __init__(self, message: str, code: int = None, original_error: Exception = None): + super().__init__(message) + self.code = code + self.original_error = original_error + + +class VolcengineCaptionService: + """ + 火山引擎音视频字幕服务封装 + """ + + # API 基础配置 + BASE_URL = "https://openspeech.bytedance.com/api/v1/vc" + DEFAULT_TIMEOUT = 60.0 + DEFAULT_POLL_INTERVAL = 1.0 + MAX_POLL_RETRIES = 120 # 最多轮询120秒 + + # 错误码映射 + ERROR_CODES = { + 0: "成功", + 2000: "处理中", + 1001: "参数无效", + 1002: "无权限", + 1003: "超频", + 1010: "音频过长", + 1011: "音频过大", + 1012: "格式无效", + 1013: "音频静音", + } + + def __init__(self, appid: str | None = None, token: str | None = None): + """ + 初始化字幕服务 + + Args: + appid: 应用ID,默认从 Settings 读取 + token: 鉴权Token,默认从 Settings 读取 + """ + settings = get_settings() + self.appid = appid or settings.VOLCENGINE_CAPTION_APPID or "" + self.token = token or settings.VOLCENGINE_CAPTION_TOKEN or "" + + if not self.appid: + raise VolcengineCaptionError("VOLCENGINE_CAPTION_APPID 未配置") + if not self.token: + raise VolcengineCaptionError("VOLCENGINE_CAPTION_TOKEN 未配置") + + self._client: httpx.AsyncClient | None = None + + async def _get_client(self) -> httpx.AsyncClient: + """获取 HTTP 客户端""" + if self._client is None or self._client.is_closed: + self._client = httpx.AsyncClient(timeout=self.DEFAULT_TIMEOUT) + return self._client + + def _get_headers(self) -> dict: + """获取请求头""" + return { + "Authorization": f"Bearer; {self.token}", + "Content-Type": "application/json", + } + + async def submit_caption_task( + self, + audio_url: str, + language: str = "zh-CN", + caption_type: str = "auto", + use_punc: bool = True, + use_itn: bool = True, + words_per_line: int = 46, + max_lines: int = 1, + ) -> str: + """ + 提交字幕生成任务 + + Args: + audio_url: 音频/视频文件URL + language: 语言代码 + caption_type: 识别类型 (auto/speech/singing) + use_punc: 自动标点 + use_itn: 数字转换 + words_per_line: 每行字数 + max_lines: 每屏行数 + + Returns: + 任务ID + + Raises: + VolcengineCaptionError: 提交失败 + """ + client = await self._get_client() + + params = { + "appid": self.appid, + "language": language, + "caption_type": caption_type, + "use_punc": str(use_punc), + "use_itn": str(use_itn), + "words_per_line": words_per_line, + "max_lines": max_lines, + } + + payload = {"url": audio_url} + + try: + response = await client.post( + f"{self.BASE_URL}/submit", + params=params, + json=payload, + headers=self._get_headers(), + ) + response.raise_for_status() + data = response.json() + + if "id" not in data: + raise VolcengineCaptionError(f"提交任务失败: {data.get('message', '未知错误')}") + + task_id = data["id"] + logger.info(f"字幕任务已提交: {task_id}") + return task_id + + except httpx.HTTPStatusError as e: + raise VolcengineCaptionError( + f"HTTP错误: {e.response.status_code}", + original_error=e, + ) + except Exception as e: + raise VolcengineCaptionError(f"提交任务失败: {str(e)}", original_error=e) + + async def query_caption_task( + self, + task_id: str, + blocking: bool = False, + ) -> CaptionResult: + """ + 查询字幕任务结果 + + Args: + task_id: 任务ID + blocking: 是否阻塞等待结果 (blocking=1) + + Returns: + 字幕结果 + + Raises: + VolcengineCaptionError: 查询失败 + """ + client = await self._get_client() + + params = { + "appid": self.appid, + "id": task_id, + "blocking": 1 if blocking else 0, + } + + try: + response = await client.get( + f"{self.BASE_URL}/query", + params=params, + headers=self._get_headers(), + ) + response.raise_for_status() + data = response.json() + + return self._parse_caption_result(data) + + except httpx.HTTPStatusError as e: + raise VolcengineCaptionError( + f"HTTP错误: {e.response.status_code}", + original_error=e, + ) + except Exception as e: + raise VolcengineCaptionError(f"查询任务失败: {str(e)}", original_error=e) + + async def generate_caption( + self, + audio_url: str, + language: str = "zh-CN", + caption_type: str = "auto", + use_punc: bool = True, + use_itn: bool = True, + words_per_line: int = 46, + max_lines: int = 1, + max_wait_time: int = 120, + ) -> CaptionResult: + """ + 生成字幕(完整流程:提交->轮询->返回结果) + + Args: + audio_url: 音频/视频文件URL + language: 语言代码 + caption_type: 识别类型 + use_punc: 自动标点 + use_itn: 数字转换 + words_per_line: 每行字数 + max_lines: 每屏行数 + max_wait_time: 最大等待时间(秒) + + Returns: + 字幕生成结果 + + Raises: + VolcengineCaptionError: 生成失败或超时 + """ + # 提交任务 + task_id = await self.submit_caption_task( + audio_url=audio_url, + language=language, + caption_type=caption_type, + use_punc=use_punc, + use_itn=use_itn, + words_per_line=words_per_line, + max_lines=max_lines, + ) + + # 轮询结果 + start_time = asyncio.get_event_loop().time() + retries = 0 + + while retries < self.MAX_POLL_RETRIES: + result = await self.query_caption_task(task_id, blocking=True) + + if result.code == 0: + logger.info(f"字幕生成完成: {task_id}, 时长: {result.duration}s") + return result + elif result.code == 2000: + # 仍在处理中 + elapsed = asyncio.get_event_loop().time() - start_time + if elapsed > max_wait_time: + raise VolcengineCaptionError(f"字幕生成超时: 已等待 {max_wait_time}s") + await asyncio.sleep(self.DEFAULT_POLL_INTERVAL) + retries += 1 + else: + # 其他错误 + error_msg = self.ERROR_CODES.get(result.code, f"未知错误: {result.code}") + raise VolcengineCaptionError( + f"字幕生成失败: {error_msg} ({result.message})", code=result.code + ) + + raise VolcengineCaptionError("字幕生成超时: 超过最大重试次数") + + async def submit_auto_align_task( + self, + audio_url: str, + audio_text: str, + caption_type: str = "speech", + sta_punc_mode: int = 3, + ) -> str: + """ + 提交自动字幕打轴任务 + + Args: + audio_url: 音频/视频文件URL + audio_text: 要打轴的字幕文本 + caption_type: 识别类型 (speech/singing) + sta_punc_mode: 标点模式 (1/2/3) + + Returns: + 任务ID + """ + client = await self._get_client() + + params = { + "appid": self.appid, + "caption_type": caption_type, + "sta_punc_mode": sta_punc_mode, + } + + payload = { + "url": audio_url, + "audio_text": audio_text, + } + + try: + response = await client.post( + f"{self.BASE_URL}/ata/submit", + params=params, + json=payload, + headers=self._get_headers(), + ) + response.raise_for_status() + data = response.json() + + if "id" not in data: + raise VolcengineCaptionError(f"提交打轴任务失败: {data.get('message', '未知错误')}") + + task_id = data["id"] + logger.info(f"打轴任务已提交: {task_id}") + return task_id + + except Exception as e: + raise VolcengineCaptionError(f"提交打轴任务失败: {str(e)}", original_error=e) + + async def query_auto_align_task( + self, + task_id: str, + blocking: bool = False, + ) -> AutoAlignResult: + """ + 查询打轴任务结果 + + Args: + task_id: 任务ID + blocking: 是否阻塞等待 + + Returns: + 打轴结果 + """ + client = await self._get_client() + + params = { + "appid": self.appid, + "id": task_id, + "blocking": 1 if blocking else 0, + } + + try: + response = await client.get( + f"{self.BASE_URL}/ata/query", + params=params, + headers=self._get_headers(), + ) + response.raise_for_status() + data = response.json() + logger.info( + f"[VolcengineCaption] Query response: {json.dumps(data, ensure_ascii=False)}" + ) + + # 解析结果(与字幕生成结果格式相同) + caption_result = self._parse_caption_result(data) + logger.info(f"[VolcengineCaption] Parsed result: {caption_result}") + logger.info( + f"[VolcengineCaption] First utterance: {caption_result.utterances[0] if caption_result.utterances else None}" + ) + return AutoAlignResult( + code=caption_result.code, + message=caption_result.message, + duration=caption_result.duration, + utterances=caption_result.utterances, + ) + + except Exception as e: + raise VolcengineCaptionError(f"查询打轴任务失败: {str(e)}", original_error=e) + + async def auto_align_caption( + self, + audio_url: str, + audio_text: str, + caption_type: str = "speech", + sta_punc_mode: int = 3, + max_wait_time: int = 120, + ) -> AutoAlignResult: + """ + 自动字幕打轴(完整流程) + + Args: + audio_url: 音频/视频文件URL + audio_text: 要打轴的字幕文本 + caption_type: 识别类型 + sta_punc_mode: 标点模式 + max_wait_time: 最大等待时间 + + Returns: + 打轴结果 + """ + task_id = await self.submit_auto_align_task( + audio_url=audio_url, + audio_text=audio_text, + caption_type=caption_type, + sta_punc_mode=sta_punc_mode, + ) + + start_time = asyncio.get_event_loop().time() + retries = 0 + + while retries < self.MAX_POLL_RETRIES: + result = await self.query_auto_align_task(task_id, blocking=True) + + if result.code == 0: + logger.info(f"打轴完成: {task_id}") + return result + elif result.code == 2000: + elapsed = asyncio.get_event_loop().time() - start_time + if elapsed > max_wait_time: + raise VolcengineCaptionError(f"打轴超时: 已等待 {max_wait_time}s") + await asyncio.sleep(self.DEFAULT_POLL_INTERVAL) + retries += 1 + else: + error_msg = self.ERROR_CODES.get(result.code, f"未知错误: {result.code}") + raise VolcengineCaptionError( + f"打轴失败: {error_msg} ({result.message})", code=result.code + ) + + raise VolcengineCaptionError("打轴超时: 超过最大重试次数") + + def _parse_caption_result(self, data: dict) -> CaptionResult: + """解析 API 响应为 CaptionResult""" + utterances = [] + logger.info(f"[VolcengineCaption] Parsing caption result: {data}") + + for u in data.get("utterances", []): + logger.info(f"[VolcengineCaption] Parsing utterance: {u}") + # 火山引擎可能返回驼峰命名或下划线命名的字段 + words = [ + CaptionWord( + text=w.get("text", ""), + start_time=w.get("start_time", 0) or w.get("startTime", 0), + end_time=w.get("end_time", 0) or w.get("endTime", 0), + ) + for w in u.get("words", []) + ] + + utterances.append( + CaptionUtterance( + text=u.get("text", ""), + start_time=u.get("start_time", 0) or u.get("startTime", 0), + end_time=u.get("end_time", 0) or u.get("endTime", 0), + words=words, + ) + ) + + result = CaptionResult( + code=data.get("code", -1), + message=data.get("message", ""), + duration=data.get("duration", 0.0), + utterances=utterances, + ) + logger.info(f"[VolcengineCaption] Parsed result: {result}") + return result + + @staticmethod + def to_srt(utterances: list[CaptionUtterance]) -> str: + """ + 将字幕时间轴转换为 SRT 格式 + + Args: + utterances: 字幕时间轴列表 + + Returns: + SRT 格式字符串 + """ + + def ms_to_time(ms: int) -> str: + """毫秒转换为 SRT 时间格式 HH:MM:SS,mmm""" + h = ms // 3600000 + m = (ms % 3600000) // 60000 + s = (ms % 60000) // 1000 + ms_remain = ms % 1000 + return f"{h:02d}:{m:02d}:{s:02d},{ms_remain:03d}" + + lines = [] + for i, u in enumerate(utterances, 1): + lines.append(str(i)) + lines.append(f"{ms_to_time(u.start_time)} --> {ms_to_time(u.end_time)}") + lines.append(u.text) + lines.append("") + + return "\n".join(lines).strip() + + @staticmethod + def to_ass( + utterances: list[CaptionUtterance], + video_width: int = 1080, + video_height: int = 1920, + ) -> str: + """ + 将字幕转换为 ASS 格式(使用抖音美好体) + + Args: + utterances: 字幕时间轴 + video_width: 视频宽度 + video_height: 视频高度 + + Returns: + ASS 格式字符串 + """ + from app.services.ass_generator import generate_ass + + return generate_ass( + utterances=utterances, + video_width=video_width, + video_height=video_height, + ) + + @staticmethod + def to_vtt(utterances: list[CaptionUtterance]) -> str: + """ + 将字幕时间轴转换为 WebVTT 格式 + + Args: + utterances: 字幕时间轴列表 + + Returns: + WebVTT 格式字符串 + """ + + def ms_to_vtt_time(ms: int) -> str: + """毫秒转换为 VTT 时间格式 HH:MM:SS.mmm""" + h = ms // 3600000 + m = (ms % 3600000) // 60000 + s = (ms % 60000) // 1000 + ms_remain = ms % 1000 + return f"{h:02d}:{m:02d}:{s:02d}.{ms_remain:03d}" + + lines = ["WEBVTT", ""] + + for u in utterances: + lines.append(f"{ms_to_vtt_time(u.start_time)} --> {ms_to_vtt_time(u.end_time)}") + lines.append(u.text) + lines.append("") + + return "\n".join(lines).strip() + + async def close(self): + """关闭 HTTP 客户端""" + if self._client and not self._client.is_closed: + await self._client.aclose() + + +# 全局服务单例 +_caption_service: VolcengineCaptionService | None = None + + +async def get_caption_service() -> VolcengineCaptionService: + """获取字幕服务单例""" + global _caption_service + if _caption_service is None: + _caption_service = VolcengineCaptionService() + return _caption_service + + +def reset_caption_service(): + """重置字幕服务单例(用于测试)""" + global _caption_service + _caption_service = None diff --git a/python-api/check_all_shots.py b/python-api/check_all_shots.py new file mode 100644 index 0000000..77191bc --- /dev/null +++ b/python-api/check_all_shots.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +""" +批量查询所有8个分镜在KlingAI的状态 +""" + +import asyncio +from app.ai.providers.klingai_provider import KlingAIProvider +from app.config import get_settings + +settings = get_settings() + +provider = KlingAIProvider({ + "access_key": settings.KLINGAI_ACCESS_KEY or "", + "secret_key": settings.KLINGAI_SECRET_KEY or "", + "base_url": "https://api-beijing.klingai.com", +}) + +# 8个任务ID +task_ids = [ + "875103915997044747", + "875103917494398981", + "875103918991761427", + "875103920820342867", + "875103922871357495", + "875103924817661991", + "875103926306492426", + "875103927824965644", +] + +async def check_all(): + for i, task_id in enumerate(task_ids): + print(f"\n=== 分镜 {i+1} - task_id={task_id} ===") + result = await provider.get_omni_video_task(task_id) + print(f"task_status: {result.get('task_status')}") + print(f"task_status_msg: {result.get('task_status_msg', '')}") + print(f"created_at: {result.get('created_at')}") + print(f"updated_at: {result.get('updated_at')}") + +if __name__ == "__main__": + asyncio.run(check_all()) diff --git a/python-api/check_task_status.py b/python-api/check_task_status.py new file mode 100644 index 0000000..11b14a2 --- /dev/null +++ b/python-api/check_task_status.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +""" +手动查询卡住的任务状态 +""" + +import asyncio +from app.ai.providers.klingai_provider import KlingAIProvider +from app.config import get_settings + +settings = get_settings() + +provider = KlingAIProvider({ + "access_key": settings.KLINGAI_ACCESS_KEY or "", + "secret_key": settings.KLINGAI_SECRET_KEY or "", + "base_url": "https://api-beijing.klingai.com", +}) + +task_id = "875095792892530770" # 卡住的第3个任务 + +async def check(): + print(f"查询任务状态: {task_id}") + result = await provider.get_omni_video_task(task_id) + print(f"返回结果:") + import json + print(json.dumps(result, indent=2, ensure_ascii=False)) + +if __name__ == "__main__": + asyncio.run(check()) diff --git a/python-api/config/ai_models.yaml b/python-api/config/ai_models.yaml new file mode 100644 index 0000000..d112a91 --- /dev/null +++ b/python-api/config/ai_models.yaml @@ -0,0 +1,239 @@ +# AI 模型配置文件 +# ================= +# 配置各平台及其可用模型 +# 修改后可通过 API 热重载,无需重启服务 +# +# ⚠️ 重要说明: +# API Key 不再在此文件配置!请通过 .env 文件设置: +# - 火山方舟:VOLCENGINE_API_KEY +# - 可灵 AI:KLINGAI_ACCESS_KEY, KLINGAI_SECRET_KEY +# +# 使用方法: +# 1. 在火山方舟控制台开通模型服务:https://console.volcengine.com/ark/ +# 2. 获取 API Key 并设置到 .env 文件:VOLCENGINE_API_KEY=your-api-key +# 3. 在模型广场开通需要的模型,复制 Model ID 填入下方配置 +# +# Model ID 格式:{模型名称}-{版本日期} +# 示例:doubao-seed-2-0-lite-260215 +# +# 官方文档:https://www.volcengine.com/docs/82379/2123245 + +# 平台配置 +platforms: + # Mock 平台(用于测试) + mock: + name: "Mock 测试平台" + provider: "mock" + priority: 999 + + # 火山方舟(字节跳动) + volcengine: + name: "火山方舟" + provider: "volcengine" + priority: 5 + # API Key 从 Settings (.env) 读取:VOLCENGINE_API_KEY + # 可选节点: + # 北京: https://ark.cn-beijing.volces.com/api/v3 (默认) + # 上海: https://ark.cn-shanghai.volces.com/api/v3 + # 广州: https://ark.cn-guangzhou.volces.com/api/v3 + base_url: "https://ark.cn-beijing.volces.com/api/v3" + + # 可灵 AI(快手) + klingai: + name: "可灵 AI" + provider: "klingai" + priority: 10 + # Access Key 和 Secret Key 从 Settings (.env) 读取: + # KLINGAI_ACCESS_KEY 和 KLINGAI_SECRET_KEY + # 注意:新系统调用域名已变更为 https://api-beijing.klingai.com + base_url: "https://api-beijing.klingai.com" + +# 模型配置 +# model_name: 填写 Model ID,格式:{模型名称}-{版本日期} +# capabilities: 能力标签 [script, polish, chat, image, embedding, vision] +models: + # Mock 模型 + mock-model: + platform_id: "mock" + model_name: "mock-model" + display_name: "Mock 测试模型" + capabilities: ["script", "polish", "chat"] + default_params: + temperature: 0.7 + max_tokens: 2000 + is_enabled: true + + # ===== 火山方舟模型 ===== + # 使用方式: + # 1. 前往方舟控制台 → 模型广场 + # 2. 找到需要的模型,点击"开通" + # 3. 复制 Model ID 填入下方的 model_name + + # 脚本生成、润色 - 高性能模型(质量优先) + doubao-seed-2-0-pro: + platform_id: "volcengine" + model_name: "doubao-seed-2-0-pro-260215" + display_name: "豆包 Seed 2.0 Pro" + capabilities: ["script", "polish", "chat"] + default_params: + temperature: 0.7 + max_tokens: 4000 + is_enabled: true + + # 脚本生成、润色 - 当前默认模型(响应快、性价比高) + deepseek-v3-2: + platform_id: "volcengine" + model_name: "deepseek-v3-2-251201" + display_name: "DeepSeek V3.2" + capabilities: ["script", "polish", "chat"] + default_params: + temperature: 0.7 + max_tokens: 4000 + is_enabled: true + cost_per_1k_input: 0.002 + cost_per_1k_output: 0.008 + max_tokens_limit: 64000 + + # 轻量级模型(备选) + doubao-seed-2-0-lite: + platform_id: "volcengine" + model_name: "doubao-seed-2-0-lite-260215" + display_name: "豆包 Seed 2.0 Lite" + capabilities: ["script", "polish", "chat"] + default_params: + temperature: 0.7 + max_tokens: 4000 + is_enabled: true + + # 长文本模型(备选) + doubao-lite-32k: + platform_id: "volcengine" + model_name: "doubao-lite-32k-240828" + display_name: "豆包 Lite 32K" + capabilities: ["polish", "chat"] + default_params: + temperature: 0.7 + max_tokens: 2000 + is_enabled: true + cost_per_1k_input: 0.0005 + cost_per_1k_output: 0.001 + max_tokens_limit: 32000 + + # ===== 可灵 AI 模型(视频/图像生成)===== + # 使用方式: + # 1. 前往可灵 AI 开发者平台 https://klingai.com/document-api + # 2. 获取 Access Key 和 Secret Key + # 3. 设置到 .env 文件:KLINGAI_ACCESS_KEY 和 KLINGAI_SECRET_KEY + + # 文生视频模型 + kling-v3: + platform_id: "klingai" + model_name: "kling-v3" + display_name: "可灵视频 v3 (最新)" + capabilities: ["video_generation", "text2video", "sound_control", "multi_shot"] + default_params: + duration: 5 + aspect_ratio: "16:9" + mode: "pro" + sound: "off" + multi_shot: false + is_enabled: true + + kling-v3-omni: + platform_id: "klingai" + model_name: "kling-v3-omni" + display_name: "可灵视频 v3 Omni" + capabilities: ["video_generation", "text2video", "sound_control", "multi_shot"] + default_params: + duration: 5 + aspect_ratio: "16:9" + mode: "pro" + sound: "off" + is_enabled: true + + kling-v2-6: + platform_id: "klingai" + model_name: "kling-v2.6" + display_name: "可灵视频 v2.6" + capabilities: ["video_generation", "text2video"] + default_params: + duration: 5 + aspect_ratio: "16:9" + mode: "pro" + is_enabled: true + + kling-v2-5-turbo: + platform_id: "klingai" + model_name: "kling-v2.5-turbo" + display_name: "可灵视频 v2.5 Turbo" + capabilities: ["video_generation", "text2video"] + default_params: + duration: 5 + aspect_ratio: "16:9" + mode: "std" + is_enabled: true + + # 图生视频模型 + kling-i2v-v3: + platform_id: "klingai" + model_name: "kling-v3" + display_name: "可灵图生视频 v3 (最新)" + capabilities: ["video_generation", "image2video", "multi_image_reference"] + default_params: + duration: 5 + mode: "pro" + multi_reference: false + is_enabled: true + + kling-i2v-v2-6: + platform_id: "klingai" + model_name: "kling-v2.6" + display_name: "可灵图生视频 v2.6" + capabilities: ["video_generation", "image2video"] + default_params: + duration: 5 + mode: "pro" + is_enabled: true + + kling-i2v-v1-6: + platform_id: "klingai" + model_name: "kling-v1-6" + display_name: "可灵图生视频 v1.6" + capabilities: ["video_generation", "image2video", "multi_image_reference"] + default_params: + duration: 5 + mode: "std" + is_enabled: true + + # 对口型模型 + kling-lip-sync: + platform_id: "klingai" + model_name: "kling-lip-sync" + display_name: "可灵对口型" + capabilities: ["lip_sync", "digital_human"] + default_params: + mode: "text2video" + voice_language: "zh" + voice_speed: 1.0 + is_enabled: true + + # 图像生成模型 + kolors-v1: + platform_id: "klingai" + model_name: "kolors-v1" + display_name: "可图 Kolors v1" + capabilities: ["image_generation", "text2image"] + default_params: + width: 1024 + height: 1024 + is_enabled: true + +# 任务类型到模型的默认映射 +task_defaults: + script: "deepseek-v3-2" # 脚本生成默认模型 (DeepSeek V3.2 - 响应更快) + polish: "deepseek-v3-2" # 润色默认模型 (DeepSeek V3.2) + chat: "deepseek-v3-2" # 聊天默认模型 (DeepSeek V3.2) + video_generation: "kling-v3" # 视频生成首选模型(v3支持多镜头、声音控制) + image2video: "kling-i2v-v3" # 图生视频首选模型 + lip_sync: "kling-lip-sync" # 对口型首选模型 + image_generation: "kolors-v1" # 图像生成首选模型 diff --git a/python-api/docker-compose.yml b/python-api/docker-compose.yml new file mode 100644 index 0000000..83d255b --- /dev/null +++ b/python-api/docker-compose.yml @@ -0,0 +1,98 @@ +services: + # PostgreSQL 数据库 + db: + image: postgres:15-alpine + container_name: meijiaka-db + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: meijiaka + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + networks: + - meijiaka-network + + # Redis 缓存 + redis: + image: redis:7-alpine + container_name: meijiaka-redis + volumes: + - redis_data:/data + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 5 + networks: + - meijiaka-network + + # FastAPI 应用(开发模式) + api: + build: + context: . + dockerfile: Dockerfile + container_name: meijiaka-api + environment: + - ENV=development + - DEBUG=true + - DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/meijiaka + - REDIS_HOST=redis + - REDIS_PORT=6379 + - REDIS_DB=0 + - SECRET_KEY=dev-secret-key-change-in-production + volumes: + - .:/app + - ~/Documents/Meijiaka:/root/Documents/Meijiaka + ports: + - "8080:8000" + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload + networks: + - meijiaka-network + + # Async Engine Scheduler: 统一调度所有第三方异步任务 + scheduler: + build: + context: . + dockerfile: Dockerfile + container_name: meijiaka-scheduler + environment: + - ENV=development + - DEBUG=true + - DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/meijiaka + - REDIS_HOST=redis + - REDIS_PORT=6379 + - REDIS_DB=0 + - SECRET_KEY=dev-secret-key-change-in-production + volumes: + - .:/app + - ~/Documents/Meijiaka:/root/Documents/Meijiaka + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + command: python -m app.scheduler.main + networks: + - meijiaka-network + +volumes: + postgres_data: + redis_data: + +networks: + meijiaka-network: + driver: bridge diff --git a/python-api/docs/token_manager.md b/python-api/docs/token_manager.md new file mode 100644 index 0000000..00ab63a --- /dev/null +++ b/python-api/docs/token_manager.md @@ -0,0 +1,300 @@ +# TokenManager - 通用 API 认证 Token 管理器 + +## 概述 + +TokenManager 是一个通用的 Token 缓存与自动刷新管理器,用于解决第三方 API 认证 Token 的有效期管理问题。主要解决以下痛点: + +1. **Token 频繁过期**:如 KlingAI 的 JWT Token 只有 30 分钟有效期 +2. **重复请求**:每次 API 调用都重新生成 Token,增加开销 +3. **并发安全**:多个并发请求同时触发 Token 刷新时的竞态条件 +4. **预热机制**:在 Token 过期前主动刷新,避免请求时等待 + +## 特性 + +- ✅ **Token 缓存**:缓存 Token 直到接近过期时间 +- ✅ **自动刷新**:Token 即将过期时自动后台刷新 +- ✅ **并发安全**:使用锁机制确保并发请求只触发一次刷新 +- ✅ **多策略支持**:内置 JWT、OAuth2 策略,支持自定义策略 +- ✅ **多实例隔离**:不同 Provider 的 Token 相互隔离 +- ✅ **预热机制**:提前刷新 Token,确保请求时始终有效 + +## 架构设计 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ TokenManager (单例) │ +├─────────────────────────────────────────────────────────────┤ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ +│ │ Token Cache │ │ Refresh Lock│ │ Background Tasks │ │ +│ │ {key: info} │ │ {key: Lock} │ │ {preemptive refresh}│ │ +│ └─────────────┘ └─────────────┘ └─────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ┌───────────────────┼───────────────────┐ + ▼ ▼ ▼ + ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ + │JWT Strategy │ │OAuth2 Str. │ │ Custom Str. │ + └─────────────┘ └─────────────┘ └─────────────┘ +``` + +## 快速开始 + +### 1. 基础用法 - 便捷函数 + +```python +from app.core.token_manager import get_jwt_token, get_oauth2_token + +# JWT Token (KlingAI 模式) +token_info = await get_jwt_token( + access_key="your_access_key", + secret_key="your_secret_key", +) +headers = {"Authorization": f"Bearer {token_info.token}"} + +# OAuth2 Token +token_info = await get_oauth2_token( + client_id="your_client_id", + client_secret="your_client_secret", + token_url="https://api.example.com/oauth2/token", +) +``` + +### 2. Provider 集成(推荐) + +```python +from app.core.token_manager import JWTTokenStrategy, TokenManager + +class KlingAIProvider: + def __init__(self, access_key: str, secret_key: str): + self._token_strategy = JWTTokenStrategy( + access_key=access_key, + secret_key=secret_key, + expires_in=1800, # 30分钟 + ) + + async def _get_headers(self) -> dict[str, str]: + """获取带认证的请求头""" + token_info = await TokenManager.get_instance().get_token(self._token_strategy) + return { + "Authorization": f"Bearer {token_info.token}", + "Content-Type": "application/json", + } + + async def api_call(self): + headers = await self._get_headers() + # 使用 headers 发起请求 + async with aiohttp.ClientSession() as session: + async with session.post(url, headers=headers, json=payload) as resp: + return await resp.json() +``` + +### 3. 自定义 Token 策略 + +```python +from app.core.token_manager import BaseTokenStrategy, TokenInfo, TokenManager + +class MyCustomStrategy(BaseTokenStrategy): + async def generate(self) -> TokenInfo: + # 实现你的 token 获取逻辑 + token = await fetch_token_from_somewhere() + return TokenInfo( + token=token, + expires_at=time.time() + 3600, + token_type="Bearer", + ) + + def get_cache_key(self) -> str: + # 返回唯一的缓存标识 + return "my_custom_key" + +# 使用 +strategy = MyCustomStrategy() +token_info = await TokenManager.get_instance().get_token(strategy) +``` + +## 核心概念 + +### TokenInfo + +Token 信息数据类: + +```python +@dataclass +class TokenInfo: + token: str # Token 字符串 + expires_at: float # 过期时间戳(秒) + token_type: str # Token 类型(默认 Bearer) + extra_data: dict # 额外数据(如 refresh_token) + + # 属性 + is_expired: bool # 是否已过期 + expires_in: float # 剩余有效时间(秒) + + # 方法 + is_near_expiry(safety_margin=300) -> bool # 是否接近过期 +``` + +### 安全边界(Safety Margin) + +为了防止 Token 在请求过程中过期,TokenManager 使用安全边界机制: + +- 默认安全边界:**5分钟(300秒)** +- 当 Token 剩余有效期小于安全边界时,视为"接近过期" +- 接近过期时会触发刷新 +- 预热机制会在 2 * 安全边界(10分钟)前后台刷新 + +``` +Token 生命周期: + +生成 ──────────────────────────────────────────> 过期 + │ │ + │<─────────── 30分钟有效期 ───────────────────>│ + │ │ + │ │<── 5分钟安全边界 ──>│ │ + │ │ │ │ + │ │ 接近过期,开始刷新 │ │ + │ │ │ │ + │ │<── 10分钟 ──>│ │ │ + │ │ │ │ │ + │ │ 后台预热任务调度 │ │ + │ │ │ │ │ + │ ▼ ▼ ▼ ▼ + 生成 预热 刷新 过期(永不发生) +``` + +## 内置策略 + +### JWTTokenStrategy + +用于 JWT 认证(如 KlingAI): + +```python +strategy = JWTTokenStrategy( + access_key="your_access_key", + secret_key="your_secret_key", + expires_in=1800, # Token 有效期(秒) + algorithm="HS256", # JWT 算法 + token_type="JWT", # Token 类型 +) +``` + +### OAuth2TokenStrategy + +用于 OAuth2 认证: + +```python +strategy = OAuth2TokenStrategy( + client_id="your_client_id", + client_secret="your_client_secret", + token_url="https://api.example.com/oauth2/token", + scope="read write", # 可选 + extra_params={}, # 额外参数 +) +``` + +## API 参考 + +### TokenManager + +```python +class TokenManager: + @classmethod + def get_instance(cls) -> TokenManager: + """获取单例实例""" + + async def get_token( + self, + strategy: BaseTokenStrategy, + force_refresh: bool = False, + ) -> TokenInfo: + """获取有效的 token""" + + async def get_token_string( + self, + strategy: BaseTokenStrategy, + force_refresh: bool = False, + ) -> str: + """获取 token 字符串(带 Bearer 前缀)""" + + async def invalidate(self, strategy: BaseTokenStrategy) -> bool: + """使缓存失效""" + + def clear(self): + """清除所有 token 缓存""" + + def get_stats(self) -> dict: + """获取缓存统计信息""" +``` + +## 并发安全机制 + +TokenManager 使用双重检查锁定(Double-Checked Locking)模式确保并发安全: + +```python +# 伪代码 +async def get_token(strategy): + cache_key = strategy.get_cache_key() + + # 第一次检查(无锁) + if cache_key in cache and not near_expiry: + return cache[cache_key] + + # 获取刷新锁 + async with refresh_locks[cache_key]: + # 第二次检查(有锁) + if cache_key in cache and not near_expiry: + return cache[cache_key] + + # 执行刷新 + new_token = await strategy.generate() + cache[cache_key] = new_token + return new_token +``` + +这样即使有 100 个并发请求同时触发 Token 刷新,也只会执行一次实际的刷新操作。 + +## 测试 + +运行测试: + +```bash +cd python-api +pytest tests/test_token_manager.py -v +``` + +测试覆盖: +- TokenInfo 数据类行为 +- JWT Token 生成 +- Token 缓存机制 +- 并发安全(10 个并发请求只生成 1 个 token) +- 强制刷新 +- 缓存失效 +- OAuth2 支持 +- 多 Provider 隔离 + +## 最佳实践 + +1. **在 Provider 初始化时创建 Strategy**:避免每次请求都创建新的 Strategy 实例 +2. **使用相同的 access_key/secret_key**:确保缓存命中 +3. **不要手动管理 Token 过期**:TokenManager 会自动处理 +4. **定期查看统计信息**:用于监控 Token 使用情况 + +```python +# 调试:查看 Token 缓存统计 +stats = TokenManager.get_instance().get_stats() +print(stats) +# { +# 'total_cached': 3, +# 'active_tasks': 3, +# 'tokens': { +# 'jwt:key1': {'expires_in': 1200, 'is_expired': False, 'is_near_expiry': False}, +# ... +# } +# } +``` + +## 扩展阅读 + +- [KlingAI API 文档](https://klingai.com/document-api) +- [JWT 规范 (RFC 7519)](https://tools.ietf.org/html/rfc7519) +- [OAuth2 规范 (RFC 6749)](https://tools.ietf.org/html/rfc6749) diff --git a/python-api/generate_kling_token.py b/python-api/generate_kling_token.py new file mode 100644 index 0000000..885af31 --- /dev/null +++ b/python-api/generate_kling_token.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +""" +生成 KlingAI JWT 认证 Token +使用环境变量中的 KLINGAI_ACCESS_KEY 和 KLINGAI_SECRET_KEY +""" + +import os +import time + +from dotenv import load_dotenv +from jose import jwt + +# 加载 .env 文件 +load_dotenv() + +access_key = os.getenv("KLINGAI_ACCESS_KEY", "") +secret_key = os.getenv("KLINGAI_SECRET_KEY", "") + +if not access_key or not secret_key: + print("错误: 请在 .env 文件中配置 KLINGAI_ACCESS_KEY 和 KLINGAI_SECRET_KEY") + exit(1) + +# JWT 生成算法与代码中一致 +headers = {"alg": "HS256", "typ": "JWT"} +current_time = int(time.time()) +payload = { + "iss": access_key, + "exp": current_time + 1800, # 30分钟过期 + "nbf": current_time - 5, # 5秒前生效 +} + +token = jwt.encode(payload, secret_key, algorithm="HS256", headers=headers) + +print("=" * 60) +print("KlingAI JWT Token 生成成功") +print("=" * 60) +print(f"Access Key: {access_key}") +print(f"生成时间: {current_time}") +print(f"过期时间: {current_time + 1800} (30分钟后)") +print() +print("Token:") +print(token) +print() +print("使用方式:") +print(f"Authorization: Bearer {token}") +print("=" * 60) diff --git a/python-api/mark_timeout_failed.py b/python-api/mark_timeout_failed.py new file mode 100644 index 0000000..76db9a5 --- /dev/null +++ b/python-api/mark_timeout_failed.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +""" +手动把卡住的任务标记失败 +""" + +import redis.asyncio as redis +import json +import asyncio +from app.config import get_settings + +async def main(): + settings = get_settings() + r = redis.from_url(settings.REDIS_URL) + + job_key = "job:task_09285f973d814364" + params_raw = await r.hget(job_key, "params") + if not params_raw: + print("Job not found") + return + + params = json.loads(params_raw) + shots = params["shots"] + + # 找到第3个分镜标记失败 + for i, shot in enumerate(shots): + if shot["id"] == "3" and shot["status"] == "submitted": + shot["status"] = "failed" + shot["error_message"] = "生成超时(手动标记失败)" + print(f"Marked shot 3 as failed") + break + + # 写回 Redis + await r.hset(job_key, "params", json.dumps(params)) + + # 释放槽位 + await r.srem("kling:video_slots", "task_09285f973d814364:3") + + print("Done") + await r.aclose() + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/python-api/pyproject.toml b/python-api/pyproject.toml new file mode 100644 index 0000000..9e3ae52 --- /dev/null +++ b/python-api/pyproject.toml @@ -0,0 +1,126 @@ +[project] +name = "meijiaka-ai-api" +version = "0.1.0" +description = "美家卡智影 - AI 视频创作后端 API" +authors = [{ name = "Meijiaka Team" }] +readme = "README.md" +requires-python = ">=3.13" +license = { text = "MIT" } +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.13", +] + +dependencies = [ + # Web 框架 (FastAPI 0.116+ 修复 Starlette 安全漏洞) + "fastapi>=0.116.0", + "uvicorn[standard]~=0.32.0", + "python-multipart~=0.0.20", + + # 认证 & 安全 + "passlib[bcrypt]~=1.7.4", + "bcrypt~=4.2.0", + + # 数据库 + "sqlalchemy[asyncio]~=2.0.36", + "asyncpg~=0.30.0", + + # 缓存 & 任务队列 + "redis~=5.2.0", + + + # 配置 & 验证 + "pydantic~=2.9.0", + "pydantic-settings~=2.6.0", + + # AI / LLM + "openai~=1.58.0", + + # 火山方舟官方 SDK(可选,如不需要可注释掉) + "volcengine-python-sdk[ark]~=5.0.0", + + # HTTP 客户端 + "httpx~=0.28.0", + "aiohttp>=3.13.4", # 安全修复:修复 CVE-2025-XXXX 系列漏洞 + + # 对象存储 + "qiniu~=7.13.0", + + # 工具 + "python-jose[cryptography]~=3.4.0", + + "pyyaml~=6.0.2", + "orjson>=3.11.0", # 安全修复:修复 CVE-2025-XXXX +] + +[project.optional-dependencies] +dev = [ + "pytest~=8.3.0", + "pytest-asyncio~=0.24.0", + "pytest-cov~=6.0.0", + "ruff~=0.8.0", + "black~=24.10.0", + "mypy~=1.14.0", + "bandit[toml]~=1.8.0", # 安全扫描 + "pip-audit~=2.7.0", # 漏洞检测 + "pre-commit~=4.0.0", # Git 钩子 +] + +[project.scripts] +meijiaka-api = "app.main:main" + +[tool.setuptools] +packages = ["app"] + +[tool.black] +line-length = 100 +target-version = ["py313"] + +[tool.ruff] +line-length = 100 +target-version = "py313" + +[tool.ruff.lint] +select = ["E", "F", "I", "N", "W", "UP", "B", "C4", "SIM"] +ignore = ["E501", "E402", "N802", "N803", "N806", "N815", "B008", "B904"] + +[tool.mypy] +python_version = "3.13" +strict = false +warn_return_any = false +warn_unused_configs = true +ignore_missing_imports = true +# 逐步修复历史遗留问题 +warn_no_return = false +check_untyped_defs = false +disallow_untyped_defs = false +disallow_incomplete_defs = false + +# ========== 重构防护网:新代码严格模式 ========== +[[tool.mypy.overrides]] +module = ["app.schemas.*", "app.crud.*", "app.scheduler.handlers.*"] +strict = true +warn_return_any = true +check_untyped_defs = true +disallow_untyped_defs = true +disallow_incomplete_defs = true + +# Redis 客户端 typing 问题(Awaitable[T] | T),暂不严格检查 +[[tool.mypy.overrides]] +module = ["app.scheduler.registry", "app.scheduler.slot_manager"] +strict = false +check_untyped_defs = false +disallow_untyped_defs = false + +[tool.pytest.ini_options] +asyncio_mode = "auto" + +[tool.bandit] +exclude_dirs = ["tests", "scripts"] +skips = ["B101", "B104", "B105", "B106", "B107", "B301", "B403", "B404", "B603", "B607"] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] diff --git a/python-api/query_kling_task.py b/python-api/query_kling_task.py new file mode 100644 index 0000000..b9dd1a0 --- /dev/null +++ b/python-api/query_kling_task.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +""" +查询 KlingAI Omni-Video 任务状态 +""" + +import asyncio +import os + +import aiohttp +from dotenv import load_dotenv +from jose import jwt + +# 加载 .env 文件 +load_dotenv() + +access_key = os.getenv("KLINGAI_ACCESS_KEY", "") +secret_key = os.getenv("KLINGAI_SECRET_KEY", "") +base_url = "https://api-beijing.klingai.com" + +if not access_key or not secret_key: + print("错误: 请在 .env 文件中配置 KLINGAI_ACCESS_KEY 和 KLINGAI_SECRET_KEY") + exit(1) + + +def generate_jwt_token() -> str: + """生成 JWT Token""" + import time + + headers = {"alg": "HS256", "typ": "JWT"} + current_time = int(time.time()) + payload = { + "iss": access_key, + "exp": current_time + 1800, + "nbf": current_time - 5, + } + return jwt.encode(payload, secret_key, algorithm="HS256", headers=headers) + + +async def query_task(task_type: str, task_id: str) -> dict: + """查询任务状态""" + token = generate_jwt_token() + url = f"{base_url}/v1/videos/{task_type}/{task_id}" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + } + + async with aiohttp.ClientSession() as session, session.get(url, headers=headers) as resp: + data = await resp.json() + return data + + +async def main(): + task_type = "image2video" + task_id = "871485500765933601" + print(f"正在查询任务: {task_type}/{task_id}") + print("-" * 60) + + result = await query_task(task_type, task_id) + code = result.get("code", -1) + message = result.get("message", "") + data = result.get("data", {}) + + print(f"响应 code: {code}") + print(f"响应 message: {message}") + print() + + if code == 0 and data: + task_status = data.get("task_status", "") + if task_status == "succeed": + print("✅ 任务已完成!") + video_url = data.get("video_url", "") + duration = data.get("duration", "") + print(f"视频 URL: {video_url}") + print(f"时长: {duration}s") + elif task_status == "processing" or task_status == "submitted": + print(f"⏳ 任务处理中... 状态: {task_status}") + elif task_status == "failed": + print("❌ 任务失败") + print(f"失败原因: {data.get('err_msg', '未知错误')}") + else: + print(f"任务状态: {task_status}") + print() + print("完整数据:") + import json + + print(json.dumps(data, indent=2, ensure_ascii=False)) + else: + print("查询失败") + print("完整响应:") + import json + + print(json.dumps(result, indent=2, ensure_ascii=False)) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/scripts/add_video_path_to_segments.py b/scripts/add_video_path_to_segments.py new file mode 100644 index 0000000..e0a25e9 --- /dev/null +++ b/scripts/add_video_path_to_segments.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +""" +给已有的 segments.json 添加 videoPath 和 videoType 字段 +保留原有的 scene/voiceover/duration 不变 +""" + +import json +from pathlib import Path + +MEIJIAKA_DIR = Path.home() / "Documents" / "Meijiaka" +PROJECTS_DIR = MEIJIAKA_DIR / "projects" + + +def add_video_path(project_id: str): + project_dir = PROJECTS_DIR / project_id + segments_file = project_dir / "segments.json" + videos_dir = project_dir / "videos" + + if not segments_file.exists(): + print(f"segments.json 不存在: {segments_file}") + return False + + if not videos_dir.exists(): + print(f"videos 目录不存在: {videos_dir}") + return False + + # 读取现有的 segments + with open(segments_file, "r", encoding="utf-8") as f: + segments = json.load(f) + + print(f"读取到 {len(segments)} 个分镜") + + # 建立文件名映射 + video_map = {} + for video_path in videos_dir.glob("*.mp4"): + stem = video_path.stem + # scene_1.mp4 -> 1 + parts = stem.split("_") + for part in reversed(parts): + if part.isdigit(): + shot_id = int(part) + video_map[shot_id] = stem + break + + print(f"找到 {len(video_map)} 个视频文件") + + # 更新每个分镜,添加 videoPath + updated = 0 + for seg in segments: + shot_id = seg["id"] + if shot_id in video_map: + # 视频已经在本地,直接用 file:// URL + full_path = videos_dir / f"{video_map[shot_id]}.mp4" + abs_path = str(full_path.absolute()) + if abs_path.startswith("/"): + video_url = "file://" + abs_path + else: + # Windows + normalized = abs_path.replace("\\", "/") + video_url = "file:///" + normalized + seg["videoPath"] = video_url + shot_type = seg.get("type", "segment") + seg["videoType"] = ( + "text2video" if shot_type == "empty_shot" else "avatar-video" + ) + updated += 1 + print(f" ✓ 镜头 {shot_id} 添加 videoPath: {video_url}") + + # 备份原文件 + backup = segments_file.with_suffix(".json.backup2") + segments_file.replace(backup) + print(f"\n原文件备份到: {backup}") + + # 保存更新后的 + with open(segments_file, "w", encoding="utf-8") as f: + json.dump(segments, f, indent=2, ensure_ascii=False) + + print(f"\n完成!更新了 {updated} 个分镜") + print(f"原文件备份在: {backup}") + return True + + +def main(): + import sys + + if len(sys.argv) != 2: + print(f"用法: python {sys.argv[0]} ") + print(f"示例: python {sys.argv[0]} proj_1775626705858_4ngn3k63z") + return + + project_id = sys.argv[1] + add_video_path(project_id) + + +if __name__ == "__main__": + main() diff --git a/scripts/fix_segments_with_videos.py b/scripts/fix_segments_with_videos.py new file mode 100644 index 0000000..7634f7b --- /dev/null +++ b/scripts/fix_segments_with_videos.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +""" +修复 segments.json,给已有分镜添加 videoPath 字段 +用法: python fix_segments_with_videos.py +""" + +import json +import sys +from pathlib import Path + +MEIJIAKA_DIR = Path.home() / "Documents" / "Meijiaka" +PROJECTS_DIR = MEIJIAKA_DIR / "projects" + + +def fix_project(project_id: str): + project_dir = PROJECTS_DIR / project_id + if not project_dir.exists(): + print(f"项目不存在: {project_dir}") + return False + + segments_file = project_dir / "segments.json" + videos_dir = project_dir / "videos" + + if not segments_file.exists(): + print(f"segments.json 不存在: {segments_file}") + return False + + if not videos_dir.exists(): + print(f"videos 目录不存在: {videos_dir}") + return False + + # 读取当前 segments + with open(segments_file, "r", encoding="utf-8") as f: + segments = json.load(f) + + print(f"读取 segments.json,共 {len(segments)} 个分镜:") + for seg in segments: + print( + f" 镜头 {seg['id']}: {seg.get('type', '?')} - {seg.get('scene', '')[:30]}..." + ) + + # 获取所有视频文件 + video_files = list(videos_dir.glob("*.mp4")) + print(f"\n找到 {len(video_files)} 个视频文件:") + + # 建立 shot_id -> filename 映射 + video_map: dict[int, str] = {} + for vf in video_files: + name = vf.name + parts = name.split("_") + for part in reversed(parts): + if part.removesuffix(".mp4").isdigit(): + shot_id = int(part.removesuffix(".mp4")) + video_map[shot_id] = name + print(f" {name} -> 镜头 {shot_id}") + break + + # 更新每个分镜 + updated = 0 + for seg in segments: + shot_id = seg["id"] + if shot_id in video_map: + filename = video_map[shot_id] + # 通过后端 API 访问,这样浏览器和 Tauri 都能工作 + video_url = f"http://127.0.0.1:8080/api/v1/video/download/{filename.removesuffix('.mp4')}" + shot_type = seg.get("type", "segment") + video_type = "text2video" if shot_type == "empty_shot" else "avatar-video" + seg["videoPath"] = video_url + seg["videoType"] = video_type + updated += 1 + print(f" ✓ 镜头 {shot_id} 添加: {video_url}") + else: + print(f" ○ 镜头 {shot_id} 没有找到对应视频文件") + + # 备份原文件 + backup = segments_file.with_suffix(".json.backup") + if not backup.exists(): + segments_file.replace(backup) + print(f"\n备份已保存到: {backup}") + + # 保存更新后的 + with open(segments_file, "w", encoding="utf-8") as f: + json.dump(segments, f, indent=2, ensure_ascii=False) + + print(f"\n完成!更新了 {updated} 个分镜的 videoPath") + print("\n请刷新浏览器页面,重新打开项目,现在点击镜头应该就能预览了") + return True + + +def main(): + if len(sys.argv) != 2: + print(f"用法: python {sys.argv[0]} ") + print(f"示例: python {sys.argv[0]} proj_1775626705858_4ngn3k63z") + return + + project_id = sys.argv[1] + fix_project(project_id) + + +if __name__ == "__main__": + main() diff --git a/scripts/recover_video_paths.py b/scripts/recover_video_paths.py new file mode 100644 index 0000000..482a31c --- /dev/null +++ b/scripts/recover_video_paths.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +""" +恢复已生成视频的路径信息 +扫描项目 videos 目录中的 .mp4 文件,并更新 segments.json 中的 videoPath +""" + +import json +from pathlib import Path +from typing import Optional + +# 基础目录 +MEIJIAKA_DIR = Path.home() / "Documents" / "Meijiaka" +PROJECTS_DIR = MEIJIAKA_DIR / "projects" + + +def parse_shot_id_from_filename(filename: str) -> Optional[int]: + """从文件名解析 shot_id""" + # scene_1.mp4 -> 1 + # scene_shot_01.mp4 -> 1 + name = filename.removesuffix(".mp4") + parts = name.split("_") + + # 反向查找,找到第一个数字部分 + for part in reversed(parts): + if part.isdigit(): + return int(part) + + return None + + +def recover_project(project_dir: Path): + """恢复单个项目的视频路径""" + meta_file = project_dir / "meta.json" + segments_file = project_dir / "segments.json" + + # 检查视频目录 + videos_dir = project_dir / "videos" + if not videos_dir.exists(): + print(f"[{project_dir.name}] 无 videos 目录,跳过") + return False + + # 获取所有 mp4 文件 + video_files = list(videos_dir.glob("*.mp4")) + if not video_files: + print(f"[{project_dir.name}] 无视频文件,跳过") + return False + + print(f"\n[{project_dir.name}] 发现 {len(video_files)} 个视频文件:") + + # 解析所有视频 + video_info = [] + for vf in video_files: + shot_id = parse_shot_id_from_filename(vf.name) + if shot_id is not None: + video_info.append((shot_id, vf)) + print(f" - {vf.name} -> 镜头 ID: {shot_id}") + else: + print(f" - {vf.name} -> 无法解析 ID,跳过") + + if not video_info: + print(f"[{project_dir.name}] 没有可恢复的视频,跳过") + return False + + # 读取现有的 segments.json + segments = [] + if segments_file.exists() and segments_file.stat().st_size > 2: + with open(segments_file, "r", encoding="utf-8") as f: + try: + segments = json.load(f) + print(f" 读取现有 segments.json,共 {len(segments)} 个分镜") + except json.JSONDecodeError: + print(" segments.json 损坏,将重新创建") + segments = [] + else: + print(" segments.json 不存在或为空,将重新创建") + segments = [] + + # 如果没有 segments,从头创建 + if not segments: + print(" 创建新的分镜数据") + for shot_id, vf in sorted(video_info, key=lambda x: x[0]): + # 判断类型:文件名中包含 empty 则为空镜 + is_empty = "empty" in vf.name or "shot" in vf.name + segment_type = "empty_shot" if is_empty else "segment" + segments.append( + { + "id": shot_id, + "type": segment_type, + "scene": "", + "voiceover": "", + "duration": "5s", + } + ) + print(f" + 镜头 {shot_id} ({segment_type})") + + # 更新 videoPath + updated = 0 + for shot_id, vf in video_info: + filename = vf.name + # 找到对应的分镜 + found = False + for seg in segments: + if seg.get("id") == shot_id: + # 确定类型 + shot_type = seg.get("type", "segment") + video_type = ( + "text2video" if shot_type == "empty_shot" else "avatar-video" + ) + # 构建可访问的 URL + video_url = f"http://127.0.0.1:8080/api/v1/video/download/{filename.removesuffix('.mp4')}" + seg["videoPath"] = video_url + seg["videoType"] = video_type + found = True + updated += 1 + print(f" ✓ 更新镜头 {shot_id}: {video_url}") + break + + if not found: + print(f" ✗ 未找到分镜 {shot_id},跳过") + + # 创建 meta.json 如果不存在 + if not meta_file.exists(): + print(" 创建默认 meta.json") + meta = { + "id": project_dir.name, + "title": f"项目 {project_dir.name}", + "status": "draft", + "createdAt": int(project_dir.stat().st_ctime), + "updatedAt": int(project_dir.stat().st_mtime), + } + with open(meta_file, "w", encoding="utf-8") as f: + json.dump(meta, f, indent=2, ensure_ascii=False) + + # 写回 segments.json + with open(segments_file, "w", encoding="utf-8") as f: + json.dump(segments, f, indent=2, ensure_ascii=False) + + print(f" [{project_dir.name}] 完成:更新 {updated} 个视频路径") + return True + + +def main(): + if not PROJECTS_DIR.exists(): + print(f"项目目录不存在: {PROJECTS_DIR}") + return + + # 遍历所有项目目录 + for project_dir in PROJECTS_DIR.iterdir(): + if project_dir.is_dir(): + recover_project(project_dir) + + print("\n=== 恢复完成 ===") + + +if __name__ == "__main__": + main() diff --git a/tauri-app/.env b/tauri-app/.env new file mode 100644 index 0000000..cd9e873 --- /dev/null +++ b/tauri-app/.env @@ -0,0 +1,3 @@ +# Vite 开发环境变量 +# 前端默认连接的 Python API 地址 +VITE_API_BASE_URL=http://127.0.0.1:8080/api/v1 diff --git a/tauri-app/.gitignore b/tauri-app/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/tauri-app/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/tauri-app/.prettierrc b/tauri-app/.prettierrc new file mode 100644 index 0000000..639e153 --- /dev/null +++ b/tauri-app/.prettierrc @@ -0,0 +1,15 @@ +{ + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "printWidth": 100, + "bracketSpacing": true, + "arrowParens": "avoid", + "endOfLine": "lf", + "useTabs": false, + "quoteProps": "as-needed", + "jsxSingleQuote": false, + "bracketSameLine": false, + "embeddedLanguageFormatting": "auto" +} diff --git a/tauri-app/.stylelintrc.json b/tauri-app/.stylelintrc.json new file mode 100644 index 0000000..aaa33fc --- /dev/null +++ b/tauri-app/.stylelintrc.json @@ -0,0 +1,22 @@ +{ + "extends": [ + "stylelint-config-standard" + ], + "rules": { + "custom-property-pattern": "^[a-z][a-z0-9-]*$", + + "declaration-property-value-disallowed-list": { + "border-radius": ["1px", "2px", "3px", "4px", "5px", "6px", "7px", "8px", "9px", "10px", "11px", "12px", "13px", "14px", "15px", "16px"], + "font-size": ["9px", "10px", "11px", "12px", "13px", "14px", "15px", "16px", "17px", "18px", "19px", "20px", "21px", "22px"] + }, + + "declaration-empty-line-before": null, + "selector-class-pattern": null, + "keyframes-name-pattern": null, + + "no-descending-specificity": null, + "declaration-block-no-duplicate-properties": null, + "number-max-precision": 4, + "max-nesting-depth": null + } +} diff --git a/tauri-app/.vscode/extensions.json b/tauri-app/.vscode/extensions.json new file mode 100644 index 0000000..0fb39b1 --- /dev/null +++ b/tauri-app/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "stylelint.vscode-stylelint", + "bradlc.vscode-tailwindcss" + ] +} diff --git a/tauri-app/AGENTS.md b/tauri-app/AGENTS.md new file mode 100644 index 0000000..49cbc3c --- /dev/null +++ b/tauri-app/AGENTS.md @@ -0,0 +1,249 @@ +# AGENTS.md — 美家卡智影 + +> 本文档面向 AI Coding Agent。项目主要使用中文进行注释和界面文案,文档亦以中文撰写。 + +--- + +## 项目概述 + +**美家卡智影**(产品名)是一款基于 Tauri v2 + React 19 + TypeScript 的桌面端 AI 视频创作应用。 + +- **产品标识**: `cn.meijiaka.ai-video` +- **版本**: `0.1.0` +- **窗口尺寸**: 1200×800,不可缩放(`resizable: false`) +- **核心功能**: AI 脚本生成、AI 配音、数字人视频生成、视频合成(FFmpeg)、项目本地持久化 + +### 技术栈 + +| 层级 | 技术 | +|------|------| +| 前端框架 | React 19 + TypeScript | +| 构建工具 | Vite 7 | +| 桌面壳 | Tauri v2 (Rust) | +| 状态管理 | Zustand 5 + Immer 11 | +| 路由 | `react-router-dom` (BrowserRouter) + 自定义 `NavigationContext` 做页面切换 | +| 数据请求 | 自研智能路由客户端 (`src/api/client.ts`) + SWR | +| 测试 | Vitest 4 + jsdom + `@testing-library/react` | +| 虚拟列表 | `@tanstack/react-virtual` | +| 外部依赖 | FFmpeg(以 Tauri Sidecar 形式打包) | + +### 运行时架构 + +采用**混合通信架构**: + +1. **纯数据 API**(脚本、配音、视频生成)→ 前端通过 HTTP **直连 Python 后端**(`http://127.0.0.1:8080/api/v1`)。 +2. **需要本地系统能力**(FFmpeg 视频合成、文件系统读写、认证)→ 走 **Tauri IPC → Rust 层** 处理。 + +> 新增纯数据 API 时,**无需修改 Rust 代码**,直接在 `src/api/modules/` 下使用 `client.post/get` 调用即可。 + +--- + +## 目录结构 + +``` +. +├── index.html # Vite 入口 HTML +├── package.json # Node 依赖与脚本 +├── vite.config.ts # Vite 配置(端口 1420,Tauri 适配) +├── vitest.config.ts # 测试配置 +├── tsconfig.json # TypeScript 主配置(strict: true) +├── tsconfig.node.json # Node 工具链 TS 配置 +├── src/ +│ ├── main.tsx # React 挂载入口 +│ ├── App.tsx # 根组件:导航上下文、主题、侧边栏布局 +│ ├── api/ +│ │ ├── client.ts # 智能路由客户端(HTTP / IPC 自动选择) +│ │ ├── types.ts # 通用 API 类型 +│ │ └── modules/ # 按领域拆分的 API 模块 +│ │ ├── auth.ts +│ │ ├── script.ts # 脚本生成(含 SSE 流式接口) +│ │ ├── voice.ts +│ │ ├── video.ts +│ │ ├── videoComposite.ts # 视频合成(走 IPC) +│ │ ├── cover.ts +│ │ └── system.ts # 项目持久化 +│ ├── components/ # 可复用组件(PascalCase 文件夹) +│ │ ├── Layout/ +│ │ ├── Modal/ +│ │ ├── Toast/ +│ │ ├── ErrorBoundary/ +│ │ ├── VirtualShotList/ +│ │ └── ... +│ ├── hooks/ # 自定义 React Hooks +│ ├── pages/ # 页面级组件(PascalCase 文件夹) +│ │ ├── VideoCreation/ # 核心创作流程(脚本→音频→封面→合成) +│ │ ├── ContentManagement/ +│ │ │ ├── VoiceClone/ +│ │ │ ├── DigitalHuman/ +│ │ │ └── MyWorks/ +│ │ ├── Settings/ +│ │ ├── Profile/ +│ │ └── Login/ +│ ├── store/ # Zustand 状态管理 +│ │ ├── authStore.ts +│ │ ├── projectStore.ts # 项目数据(分镜、空镜、音频、封面等) +│ │ ├── settingsStore.ts +│ │ ├── uiStore.ts # Toast 等 UI 状态 +│ │ ├── Provider.tsx +│ │ ├── index.ts +│ │ └── __tests__/ # Store 单元测试 +│ ├── styles/ +│ │ ├── variables.css # CSS 变量(含主题变量) +│ │ └── global.css # 全局样式 +│ └── __tests__/ +│ └── setup.ts # Vitest 全局 setup(mock localStorage / Tauri API) +├── src-tauri/ +│ ├── Cargo.toml # Rust 依赖 +│ ├── tauri.conf.json # Tauri 应用配置(CSP、窗口、打包、sidecar) +│ ├── build.rs +│ ├── binaries/ffmpeg # FFmpeg sidecar 二进制 +│ ├── icons/ # 应用图标 +│ └── src/ +│ ├── main.rs # 程序入口 +│ ├── lib.rs # Tauri Builder、Command 定义、Python 代理 +│ ├── ffmpeg_cmd.rs # FFmpeg 命令封装(拼接、音画合并、封面转视频) +│ └── persistence.rs # 项目本地文件读写 +└── public/ # 静态资源 +``` + +--- + +## 构建与开发命令 + +```bash +# 前端开发服务器(Vite,端口 1420) +npm run dev + +# 生产构建(tsc + vite build) +npm run build + +# Tauri 开发(启动 Rust + 前端) +npm run tauri dev + +# Tauri 生产打包 +npm run tauri build + +# 运行测试 +npm run test + +# Vitest UI 模式 +npm run test:ui + +# 测试覆盖率 +npm run test:coverage +``` + +### 关键配置说明 + +- **Vite 端口固定为 1420**(`strictPort: true`),与 Tauri `devUrl` 对齐。 +- **Tauri 开发时忽略 `src-tauri/` 目录的 watch**,避免前端热更新与 Rust 编译互相干扰。 +- **路径别名**: `@/` 映射到 `./src`(在 `vitest.config.ts` 中配置)。 + +--- + +## 代码风格与约定 + +### 命名规范 + +- **组件/页面文件夹**: `PascalCase`(如 `VideoCreation`、`ErrorBoundary`) +- **Store/Hooks/API 文件**: `camelCase`(如 `authStore.ts`、`useProjectData.ts`) +- **类型/接口**: `PascalCase` +- **常量**: `UPPER_SNAKE_CASE`(Rust 层) + +### 注释语言 + +- 项目内**统一使用中文注释**。 +- 关键架构决策需在代码中以多行注释说明(参考 `src/api/client.ts` 顶部的“混合模式智能路由”注释)。 + +### TypeScript 配置 + +- `strict: true` 已开启。 +- `noUnusedLocals: true`、`noUnusedParameters: true` 已开启,未使用变量会报错。 +- `jsx: "react-jsx"`,无需手动引入 `React`。 + +### 状态管理约定 + +- 使用 **Zustand + Immer** 进行不可变更新。 +- `projectStore` 使用自定义 `persist` 存储,将项目数据通过 Tauri IPC 持久化到本地文件系统(`app_config_dir/current_project.json`),而不是 localStorage。 +- 其他 Store(如 `authStore`、`settingsStore`)使用 `localStorage` 做持久化。 + +### API 开发流程 + +1. **判断是否需要本地能力**(FFmpeg、文件系统、系统调用)。 +2. **不需要** → 直接在 `src/api/modules/` 使用 `client.get/post/put/delete` 调用 Python HTTP API。 +3. **需要** → 将 endpoint 加入 `src/api/client.ts` 的 `RUST_IPC_APIS` 集合,并在 `src-tauri/src/lib.rs` 中实现对应的 `#[tauri::command]` 处理器。 + +--- + +## 测试说明 + +### 测试框架 + +- **Vitest**(globals: true,environment: `jsdom`) +- **@testing-library/react** 用于测试 Hooks 和组件 +- **@testing-library/jest-dom** 提供自定义 matchers + +### 测试文件位置 + +- 全局 setup: `src/__tests__/setup.ts` +- Store 测试: `src/store/__tests__/*.test.tsx` +- 组件/页面测试: 建议放在被测文件同目录或 `__tests__` 子目录中 + +### Mock 策略 + +`setup.ts` 中已全局 mock 以下内容: + +- `localStorage`(完整 mock) +- `@tauri-apps/api/core` 的 `invoke` 方法 +- `window.__TAURI_INTERNALS__` + +每个测试后自动调用 `vi.clearAllMocks()`。 + +### 运行示例 + +```bash +# 单次运行 +npm run test + +# 交互式 UI +npm run test:ui +``` + +--- + +## 安全与部署 + +### CSP 配置 + +`src-tauri/tauri.conf.json` 中已配置 Content Security Policy: + +- `default-src`: `'self'` +- `connect-src`: `'self' http://localhost:8080 http://127.0.0.1:8080` +- `img-src` / `media-src`: 允许 `http://localhost:8080` 及 `blob:`、`data:` + +### 外部二进制 + +- FFmpeg 作为 **sidecar** 打包(`bundle.externalBin: ["binaries/ffmpeg"]`)。 +- Rust 层通过 `tauri_plugin_shell` 的 `sidecar("ffmpeg")` 调用。 +- 合成过程中会解析 FFmpeg stderr 输出中的 `time=` 字段,并通过 Tauri Event (`ffmpeg-progress`) 向前端发送进度。 + +### 项目文件存储路径 + +- **项目持久化文件**: `{app_config_dir}/current_project.json`(由 `persistence.rs` 管理)。 +- **合成临时文件**: `{document_dir}/Meijiaka/Projects/`(由 `lib.rs` 中的 `get_project_dir` 管理)。 + +### 认证状态 + +当前登录接口 (`auth_login`) 返回 **Mock 数据**,仅用于开发阶段。生产环境需替换为真实认证逻辑。 + +--- + +## 给 Agent 的快速检查清单 + +在修改代码前,建议确认以下事项: + +1. **新增 API 是否需要 Rust 层?** 不需要则只改前端 `src/api/modules/`。 +2. **修改 Store 后是否影响持久化?** `projectStore` 的 `partialize` 字段决定哪些状态会被保存到本地文件。 +3. **新增组件是否遵循 PascalCase 文件夹约定?** +4. **测试是否通过?** 运行 `npm run test`。 +5. **Tauri 配置变更后是否需要重新 `tauri dev`?** 是的,`tauri.conf.json` 或 `Cargo.toml` 变更后需重启 Tauri 进程。 diff --git a/tauri-app/README.md b/tauri-app/README.md new file mode 100644 index 0000000..102e366 --- /dev/null +++ b/tauri-app/README.md @@ -0,0 +1,7 @@ +# Tauri + React + Typescript + +This template should help get you started developing with Tauri, React and Typescript in Vite. + +## Recommended IDE Setup + +- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) diff --git a/tauri-app/assets/subtitle-template.ass b/tauri-app/assets/subtitle-template.ass new file mode 100644 index 0000000..44b0e8e --- /dev/null +++ b/tauri-app/assets/subtitle-template.ass @@ -0,0 +1,19 @@ +[Script Info] +; Script generated by Meijiaka AI Video +; Style: 抖音美好体 - 抖音风格字幕 +ScriptType: v4.00+ +PlayResX: 1920 +PlayResY: 1080 +ScaledBorderAndShadow: yes +Video Aspect Ratio: 0 +Video Rate: 25 +Audio Rate: 48000 + +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: Douyin-Diamond,抖音美好体,72,&H00FFFFFF,&H00000000,&H00141414,&H00000000,1,0,0,0,100,100,0,0,1,3,0,2,20,20,80,1 +Style: Douyin-Bold,抖音美好体,64,&H00FFFFFF,&H00000000,&H00141414,&H00000000,1,0,0,0,100,100,0,0,1,2,0,2,20,20,60,1 +Style: Douyin-Small,抖音美好体,54,&H00EEEEEE,&H00000000,&H00141414,&H00000000,1,0,0,0,100,100,0,0,1,2,0,2,20,20,40,1 + +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text diff --git a/tauri-app/docs/data-architecture.md b/tauri-app/docs/data-architecture.md new file mode 100644 index 0000000..a48a436 --- /dev/null +++ b/tauri-app/docs/data-architecture.md @@ -0,0 +1,232 @@ +# 美家卡智影 - 数据架构设计 + +## 1. 架构目标 + +- **离线优先**:无网络时也能正常使用 +- **云端同步**:多设备间数据同步 +- **多媒体本地存储**:大文件存本地目录,小数据存云端 +- **自动冲突解决**:简化用户操作 + +## 2. 数据分层 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 云端 (PostgreSQL) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ users │ │ projects │ │ avatars │ ... │ +│ │ (用户账号) │ │ (项目元数据) │ │ (形象元数据) │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ 小数据、结构数据、需要同步的数据 │ +└─────────────────────────────────────────────────────────────────┘ + ↑↓ 同步 +┌─────────────────────────────────────────────────────────────────┐ +│ 本地 (Tauri + 浏览器) │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ SQLite / 文件系统 (~/Documents/Meijiaka/) │ │ +│ │ ├─ projects/ # 项目数据 │ │ +│ │ │ └─ {project_id}/ │ │ +│ │ │ ├─ project.json # 项目配置 │ │ +│ │ │ ├─ segments/ # 分镜数据 │ │ +│ │ │ ├─ videos/ # 生成的视频 │ │ +│ │ │ └─ covers/ # 封面图片 │ │ +│ │ ├─ avatars/ # 克隆形象 │ │ +│ │ │ └─ {avatar_id}/ │ │ +│ │ │ ├─ avatar.json # 形象配置 │ │ +│ │ │ └─ video.mp4 # 形象视频 │ │ +│ │ └─ temp/ # 临时文件 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ SWR Cache (内存) │ │ +│ │ - API 响应缓存 │ │ +│ │ - 乐观更新队列 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## 3. 数据分类 + +### 3.1 云端优先数据(小数据,结构化) + +| 数据类型 | 存储位置 | 同步策略 | 说明 | +|---------|---------|---------|------| +| 用户信息 | 云端 | 实时 | 账号、设置偏好 | +| 项目列表 | 云端 | 实时 | 项目ID、标题、状态、更新时间 | +| 形象元数据 | 云端 | 实时 | 形象ID、名称、云端视频URL | +| 成本统计 | 云端 | 实时 | API 调用记录、费用 | +| 脚本内容 | 云端+本地 | 双向同步 | 分镜文案、场景描述 | + +### 3.2 本地优先数据(大文件,多媒体) + +| 数据类型 | 存储位置 | 同步策略 | 说明 | +|---------|---------|---------|------| +| 形象视频 | 本地 | 按需上传 | 克隆形象的原视频文件 | +| 生成视频 | 本地 | 可选上传 | AI生成的分镜视频 | +| 封面图片 | 本地 | 可选上传 | 项目封面 | +| 合成视频 | 本地 | 可选上传 | 最终成片 | +| 临时文件 | 本地 | 不同步 | 处理过程中的缓存 | + +## 4. 同步策略 + +### 4.1 自动同步(实时) +```typescript +// 云端优先数据 - 自动同步 +- 用户修改脚本 → 立即保存到云端 +- 切换形象 → 立即同步到云端 +- 项目状态变更 → 实时更新 +``` + +### 4.2 手动同步(用户触发) +```typescript +// 大文件 - 手动同步 +- 上传形象视频到云端备份 +- 下载云端形象到本地 +- 分享项目(打包上传) +``` + +### 4.3 离线队列 +```typescript +// 离线时操作进入队列 +interface OfflineQueue { + id: string; + type: 'create' | 'update' | 'delete'; + entity: 'project' | 'segment' | 'avatar'; + data: any; + timestamp: number; + retryCount: number; +} +``` + +## 5. 冲突解决 + +### 5.1 简单策略:最后写入优先 +```typescript +// 基于 updatedAt 时间戳 +const resolveConflict = (local: Data, remote: Data) => { + return local.updatedAt > remote.updatedAt ? local : remote; +}; +``` + +### 5.2 字段级合并(可选) +```typescript +// 不同字段分别决定 +const mergeConflict = (local: Project, remote: Project) => { + return { + ...remote, + segments: local.updatedAt > remote.updatedAt + ? local.segments + : remote.segments, + coverPath: local.coverUpdatedAt > remote.coverUpdatedAt + ? local.coverPath + : remote.coverPath, + }; +}; +``` + +## 6. 存储实现 + +### 6.1 本地目录结构 +``` +~/Documents/Meijiaka/ # 用户文档目录 +├── config.json # 全局配置 +├── cache/ # 缓存目录 +│ └── swr/ # SWR 缓存 +├── projects/ # 项目数据 +│ └── {project_id}/ +│ ├── meta.json # 项目元数据 +│ ├── segments.json # 分镜数据 +│ ├── assets/ # 资源文件 +│ │ ├── videos/ # 生成视频 +│ │ ├── covers/ # 封面图片 +│ │ └── temp/ # 临时文件 +│ └── exports/ # 导出文件 +├── avatars/ # 克隆形象 +│ └── {avatar_id}/ +│ ├── meta.json # 形象配置 +│ └── source.mp4 # 原视频 +└── voices/ # 克隆音色 + └── {voice_id}/ + └── sample.mp3 +``` + +### 6.2 数据存储接口 +```typescript +// 统一存储接口 +interface StorageAdapter { + // 项目数据 + saveProject(project: Project): Promise; + loadProject(projectId: string): Promise; + deleteProject(projectId: string): Promise; + listProjects(): Promise; + + // 形象数据 + saveAvatar(avatar: Avatar): Promise; + loadAvatar(avatarId: string): Promise; + deleteAvatar(avatarId: string): Promise; + listAvatars(): Promise; + + // 多媒体文件 + saveMedia(projectId: string, file: File, type: MediaType): Promise; + loadMedia(path: string): Promise; + deleteMedia(path: string): Promise; +} +``` + +## 7. 状态管理 + +### 7.1 分层状态 +```typescript +// 1. 服务端状态 (SWR) +const { data: projects } = useSWR('/api/projects', fetcher); + +// 2. 本地持久化状态 (Zustand + Storage) +const projectStore = useProjectStore(); // 自动持久化到本地文件 + +// 3. 临时 UI 状态 (React State) +const [selectedTab, setSelectedTab] = useState('script'); +``` + +### 7.2 同步 hook +```typescript +// 自动同步 hook +function useSyncProject(projectId: string) { + const { data: remoteProject } = useSWR(`/api/projects/${projectId}`); + const localProject = useProjectStore(state => state.project); + const sync = useProjectStore(state => state.sync); + + useEffect(() => { + if (remoteProject && localProject) { + sync(remoteProject, localProject); + } + }, [remoteProject, localProject]); +} +``` + +## 8. 迁移路径 + +### Phase 1: 本地文件存储(当前) +- [x] 项目数据存 Tauri 本地 +- [x] 形象数据存 localStorage + +### Phase 2: 云端同步(下一步) +- [ ] 后端新增 project/avatar 表 +- [ ] 实现双向同步逻辑 +- [ ] 离线队列 + +### Phase 3: 多媒体管理 +- [ ] 本地目录结构 +- [ ] 文件导入/导出 +- [ ] 云端备份(可选) + +## 9. 关键决策 + +### Q1: 是否需要本地 SQLite? +**建议**:Phase 1 不需要,JSON 文件足够简单。Phase 2 如需复杂查询再引入。 + +### Q2: 如何处理大文件同步? +**建议**:不上传大文件,只在本地存储。用户需要跨设备时,提供"导出/导入"功能。 + +### Q3: 离线支持到什么程度? +**建议**: +- 脚本编辑:完全离线 +- 视频生成:需要联网(AI 服务) +- 视频合成:可离线(本地 FFmpeg) diff --git a/tauri-app/docs/spacing-guide.md b/tauri-app/docs/spacing-guide.md new file mode 100644 index 0000000..6d18cdc --- /dev/null +++ b/tauri-app/docs/spacing-guide.md @@ -0,0 +1,95 @@ +# 间距规范指南 (Spacing Guide) + +## 间距系统 + +基于 4px 网格系统,提供统一的视觉节奏: + +| 变量 | 值 | 使用场景 | +|------|-----|----------| +| `--spacing-2xs` | 2px | 微调控件、边框线、极细间距 | +| `--spacing-xs` | 4px | 紧凑间隙、图标边距、行内元素 | +| `--spacing-sm` | 8px | 小间隙、按钮内边距-y、列表项 | +| `--spacing-md` | 12px | 标准间隙、卡片内边距、表单字段 | +| `--spacing-lg` | 16px | 大间隙、区块间距、内容分组 | +| `--spacing-xl` | 24px | 页面区块、主要内容分隔 | +| `--spacing-2xl` | 32px | 大区块间距、页面边距 | +| `--spacing-3xl` | 48px | 页面级间距、Hero 区域 | + +## 使用原则 + +1. **优先使用变量**,禁止随意写死数值 +2. **就近取整**:6px → 4px 或 8px;10px → 8px 或 12px +3. **保持一致性**:同一场景下使用相同间距 +4. **响应式设计**:大屏适当增加,小屏适当减少 + +## 常见场景规范 + +### 组件间距 +```css +/* 按钮内边距 */ +.btn-sm { padding: var(--spacing-xs) var(--spacing-sm); } /* 4px 8px */ +.btn-md { padding: var(--spacing-sm) var(--spacing-md); } /* 8px 12px */ +.btn-lg { padding: var(--spacing-sm) var(--spacing-lg); } /* 8px 16px */ + +/* 卡片内边距 */ +.card-sm { padding: var(--spacing-sm); } /* 8px */ +.card-md { padding: var(--spacing-md); } /* 12px */ +.card-lg { padding: var(--spacing-lg); } /* 16px */ + +/* 列表间隙 */ +.list-xs { gap: var(--spacing-xs); } /* 4px */ +.list-sm { gap: var(--spacing-sm); } /* 8px */ +.list-md { gap: var(--spacing-md); } /* 12px */ +``` + +### 页面布局 +```css +/* 页面边距 */ +.page-padding { padding: var(--spacing-lg) var(--spacing-xl); } /* 16px 24px */ + +/* 区块间距 */ +.section-gap { margin-bottom: var(--spacing-xl); } /* 24px */ + +/* 表单字段间距 */ +.form-field-gap { margin-bottom: var(--spacing-md); } /* 12px */ +``` + +## 迁移指南 + +| 旧值 | 新值 | 说明 | +|------|------|------| +| 2px | `--spacing-2xs` | 直接替换 | +| 4px | `--spacing-xs` | 直接替换 | +| 6px | `--spacing-xs` 或 `--spacing-sm` | 根据视觉轻重选择 4px 或 8px | +| 8px | `--spacing-sm` | 直接替换 | +| 10px | `--spacing-sm` 或 `--spacing-md` | 选择 8px 或 12px | +| 12px | `--spacing-md` | 直接替换 | +| 14px | `--spacing-md` 或 `--spacing-lg` | 选择 12px 或 16px | +| 15px | `--spacing-lg` | 改为 16px | +| 16px | `--spacing-lg` | 直接替换 | +| 18px | `--spacing-lg` | 改为 16px | +| 20px | `--spacing-xl` | 改为 24px 或保留特殊情况 | +| 24px | `--spacing-xl` | 直接替换 | +| 32px | `--spacing-2xl` | 直接替换 | +| 40px | `--spacing-2xl` 或 `--spacing-3xl` | 选择 32px 或 48px | +| 48px | `--spacing-3xl` | 直接替换 | + +## 迁移进度 + +### ✅ 已完成 +- [x] variables.css - 添加 --spacing-2xs: 2px +- [x] ContentManagement.css - 全部标准化 +- [x] VideoGeneration.css - 主要间距标准化 +- [x] ScriptCreation.css - 间距标准化 +- [x] global.css - Badge 组件间距标准化 +- [x] Toast.css - 间距标准化 +- [x] CoverDesign.tsx - 间距标准化 +- [x] VideoGeneration.tsx - 间距标准化 +- [x] ScriptCreation.tsx - 间距标准化 +- [x] VideoCompositeDebug.tsx - 间距标准化 +- [x] VideoComposite.tsx - 间距标准化 +- [x] AvatarUploadModal.tsx - 间距标准化 + +### 🔄 待处理(设计相关,需单独评估) +- [ ] VideoCreation.css - 封面模板样式(约 25 处,有设计注释) + - 这些值用于视觉对齐,需要设计师确认后再调整 diff --git a/tauri-app/eslint.config.js b/tauri-app/eslint.config.js new file mode 100644 index 0000000..c8d5a52 --- /dev/null +++ b/tauri-app/eslint.config.js @@ -0,0 +1,57 @@ +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import react from 'eslint-plugin-react'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; + +export default tseslint.config( + // 全局忽略 + { + ignores: ['dist', 'node_modules', 'src-tauri', 'src/api/generated', 'src/_unused'], + }, + + // 基础规则 + js.configs.recommended, + ...tseslint.configs.recommended, + + // React + TypeScript 文件 + { + files: ['**/*.{ts,tsx}'], + plugins: { + react, + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + settings: { + react: { + version: 'detect', + }, + }, + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + rules: { + // React + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + ...reactHooks.configs.recommended.rules, + 'react-hooks/set-state-in-effect': 'warn', + 'react-refresh/only-export-components': 'warn', + 'react/no-unescaped-entities': 'warn', + + // TypeScript + '@typescript-eslint/ban-ts-comment': 'warn', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + + // 通用 + 'no-console': ['warn', { allow: ['warn', 'error', 'info'] }], + 'no-constant-condition': 'warn', + eqeqeq: ['warn', 'always'], + curly: ['warn', 'all'], + 'no-var': 'error', + 'prefer-const': 'warn', + }, + }, +); diff --git a/tauri-app/index.html b/tauri-app/index.html new file mode 100644 index 0000000..080af10 --- /dev/null +++ b/tauri-app/index.html @@ -0,0 +1,24 @@ + + + + + + + + 美家卡 智影 + + + + +
+ + + + \ No newline at end of file diff --git a/tauri-app/package-lock.json b/tauri-app/package-lock.json new file mode 100644 index 0000000..429c685 --- /dev/null +++ b/tauri-app/package-lock.json @@ -0,0 +1,8172 @@ +{ + "name": "tauri-app", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tauri-app", + "version": "0.1.0", + "dependencies": { + "@tanstack/react-virtual": "^3.13.23", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "^2.7.0", + "@tauri-apps/plugin-fs": "^2.5.0", + "@tauri-apps/plugin-opener": "^2", + "assjs": "^0.1.5", + "immer": "^11.1.4", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.13.1", + "swr": "^2.4.1", + "zustand": "^5.0.12" + }, + "devDependencies": { + "@tauri-apps/cli": "^2", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "eslint": "^9.39.4", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.5.2", + "jsdom": "^29.0.1", + "openapi-typescript": "^7.13.0", + "prettier": "^3.2.5", + "stylelint": "^16.2.1", + "stylelint-config-standard": "^36.0.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.58.0", + "vite": "^7.3.2", + "vitest": "^4.1.1" + }, + "engines": { + "node": ">=20.0.0", + "npm": ">=10.0.0" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", + "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.4.tgz", + "integrity": "sha512-jXR6x4AcT3eIrS2fSNAwJpwirOkGcd+E7F7CP3zjdTqz9B/2huHOL8YJZBgekKwLML+u7qB/6P1LXQuMScsx0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@cacheable/memory": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.8.tgz", + "integrity": "sha512-FvEb29x5wVwu/Kf93IWwsOOEuhHh6dYCJF3vcKLzXc0KXIW181AOzv6ceT4ZpBHDvAfG60eqb+ekmrnLHIy+jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.4.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/memory/node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/memory/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.4.1.tgz", + "integrity": "sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz", + "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@dual-bundle/import-meta-resolve": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.2.1.tgz", + "integrity": "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/JounQin" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@redocly/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js-replace": "^1.0.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@redocly/config": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz", + "integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/openapi-core": { + "version": "1.34.11", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.11.tgz", + "integrity": "sha512-V09ayfnb5GyysmvARbt+voFZAjGcf7hSYxOYxSkCc4fbH/DTfq5YWoec8cflvmHHqyIFbqvmGKmYFzqhr9zxDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/ajv": "8.11.2", + "@redocly/config": "0.22.0", + "colorette": "1.4.0", + "https-proxy-agent": "7.0.6", + "js-levenshtein": "1.1.6", + "js-yaml": "4.1.1", + "minimatch": "5.1.9", + "pluralize": "8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "engines": { + "node": ">=18.17.0", + "npm": ">=9.5.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.23", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.23.tgz", + "integrity": "sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.13.23" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.23", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.23.tgz", + "integrity": "sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tauri-apps/api": { + "version": "2.10.1", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.10.0", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.10.0", + "@tauri-apps/cli-darwin-x64": "2.10.0", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", + "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", + "@tauri-apps/cli-linux-arm64-musl": "2.10.0", + "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", + "@tauri-apps/cli-linux-x64-gnu": "2.10.0", + "@tauri-apps/cli-linux-x64-musl": "2.10.0", + "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", + "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", + "@tauri-apps/cli-win32-x64-msvc": "2.10.0" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.10.0", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.0.tgz", + "integrity": "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.0.tgz", + "integrity": "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.0.tgz", + "integrity": "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.0.tgz", + "integrity": "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.0.tgz", + "integrity": "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.0.tgz", + "integrity": "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.0.tgz", + "integrity": "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.0.tgz", + "integrity": "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.0.tgz", + "integrity": "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.0.tgz", + "integrity": "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/plugin-dialog": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.7.0.tgz", + "integrity": "sha512-4nS/hfGMGCXiAS3LtVjH9AgsSAPJeG/7R+q8agTFqytjnMa4Zq95Bq8WzVDkckpanX+yyRHXnRtrKXkANKDHvw==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.10.1" + } + }, + "node_modules/@tauri-apps/plugin-fs": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.5.0.tgz", + "integrity": "sha512-c83kbz61AK+rKjhS+je9+stIO27nXj7p9cqeg36TwkIUtxpCFTttlHHtqon6h6FN54cXjyAjlMPOJcW3mwE5XQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.10.1" + } + }, + "node_modules/@tauri-apps/plugin-opener": { + "version": "2.5.3", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", + "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/type-utils": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.58.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", + "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", + "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.0", + "@typescript-eslint/types": "^8.58.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", + "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", + "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", + "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", + "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", + "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.58.0", + "@typescript-eslint/tsconfig-utils": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", + "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", + "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.1.tgz", + "integrity": "sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.1", + "@vitest/utils": "4.1.1", + "chai": "^6.2.2", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.1.tgz", + "integrity": "sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.1", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.1.tgz", + "integrity": "sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.1.tgz", + "integrity": "sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.1", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.1.tgz", + "integrity": "sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.1", + "@vitest/utils": "4.1.1", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.1.tgz", + "integrity": "sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.1.tgz", + "integrity": "sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.1", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/assjs": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assjs/-/assjs-0.1.5.tgz", + "integrity": "sha512-xJMpkFQU16scCMAK4wNnkVO/rhZnMqrwUTmlAVoEmzg5Ywl8LvZX1yWwAvQ1X4eRaGyIa7UJ1iZcKtbzmelEDg==", + "license": "MIT" + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/cacheable": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.4.tgz", + "integrity": "sha512-djgxybDbw9fL/ZWMI3+CE8ZilNxcwFkVtDc1gJ+IlOSSWkSMPQabhV/XCHTQ6pwwN6aivXPZ43omTooZiX06Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.0.8", + "@cacheable/utils": "^2.4.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.9.0" + } + }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001775", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-functions-list": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.3.3.tgz", + "integrity": "sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.302", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.1.tgz", + "integrity": "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true, + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz", + "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.0.1", + "@asamuzakjp/dom-selector": "^7.0.3", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.1", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.24.5", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/known-css-properties": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/openapi-typescript": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.13.0.tgz", + "integrity": "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/openapi-core": "^1.34.6", + "ansi-colors": "^4.1.3", + "change-case": "^5.4.4", + "parse-json": "^8.3.0", + "supports-color": "^10.2.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "openapi-typescript": "bin/cli.js" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", + "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qified": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.9.1.tgz", + "integrity": "sha512-n7mar4T0xQ+39dE2vGTAlbxUEpndwPANH0kDef1/MYsB8Bba9wshkybIRx74qgcvKQPEWErf9AqAdYjhzY2Ilg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.1.1.tgz", + "integrity": "sha512-AHb76R16GB5EsPBE2J7Ko5kiEyXwviB9P5SMrAKcuAu4vJPZttViAbj9+tZeaQE5zjDme+1vcHP78Yj/WoAveA==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.4", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", + "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz", + "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==", + "license": "MIT", + "dependencies": { + "react-router": "7.13.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint": { + "version": "16.26.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.26.1.tgz", + "integrity": "sha512-v20V59/crfc8sVTAtge0mdafI3AdnzQ2KsWe6v523L4OA1bJO02S7MO2oyXDCS6iWb9ckIPnqAFVItqSBQr7jw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-syntax-patches-for-csstree": "^1.0.19", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3", + "@csstools/selector-specificity": "^5.0.0", + "@dual-bundle/import-meta-resolve": "^4.2.1", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^9.0.0", + "css-functions-list": "^3.2.3", + "css-tree": "^3.1.0", + "debug": "^4.4.3", + "fast-glob": "^3.3.3", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^11.1.1", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.3.1", + "ignore": "^7.0.5", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.37.0", + "mathml-tag-names": "^2.1.3", + "meow": "^13.2.0", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.5.6", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.0", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "supports-hyperlinks": "^3.2.0", + "svg-tags": "^1.0.0", + "table": "^6.9.0", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "stylelint": "bin/stylelint.mjs" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz", + "integrity": "sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.1.0" + } + }, + "node_modules/stylelint-config-standard": { + "version": "36.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-36.0.1.tgz", + "integrity": "sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "dependencies": { + "stylelint-config-recommended": "^14.0.1" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.1.0" + } + }, + "node_modules/stylelint/node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/stylelint/node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/stylelint/node_modules/@csstools/media-query-list-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", + "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.2.tgz", + "integrity": "sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^6.1.20" + } + }, + "node_modules/stylelint/node_modules/flat-cache": { + "version": "6.1.22", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.22.tgz", + "integrity": "sha512-N2dnzVJIphnNsjHcrxGW7DePckJ6haPrSFqpsBUhHYgwtKGVq4JrBGielEGD2fCVnsGm1zlBVZ8wGhkyuetgug==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^2.3.4", + "flatted": "^3.4.2", + "hookified": "^1.15.0" + } + }, + "node_modules/stylelint/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "node_modules/swr": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.4.1.tgz", + "integrity": "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz", + "integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.27" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz", + "integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz", + "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.0", + "@typescript-eslint/parser": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", + "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js-replace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz", + "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.1.tgz", + "integrity": "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.1", + "@vitest/mocker": "4.1.1", + "@vitest/pretty-format": "4.1.1", + "@vitest/runner": "4.1.1", + "@vitest/snapshot": "4.1.1", + "@vitest/spy": "4.1.1", + "@vitest/utils": "4.1.1", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.1", + "@vitest/browser-preview": "4.1.1", + "@vitest/browser-webdriverio": "4.1.1", + "@vitest/ui": "4.1.1", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zustand": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", + "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/tauri-app/package.json b/tauri-app/package.json new file mode 100644 index 0000000..b9f7da3 --- /dev/null +++ b/tauri-app/package.json @@ -0,0 +1,61 @@ +{ + "name": "tauri-app", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "tauri": "tauri", + "test": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest --coverage", + "gen:api": "openapi-typescript src/api/generated/openapi.json -o src/api/generated/schema.ts", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write \"src/**/*.{ts,tsx,css}\"", + "format:check": "prettier --check \"src/**/*.{ts,tsx,css}\"", + "stylelint": "stylelint \"src/**/*.css\"", + "stylelint:fix": "stylelint \"src/**/*.css\" --fix" + }, + "dependencies": { + "@tanstack/react-virtual": "^3.13.23", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "^2.7.0", + "@tauri-apps/plugin-fs": "^2.5.0", + "@tauri-apps/plugin-opener": "^2", + "assjs": "^0.1.5", + "immer": "^11.1.4", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.13.1", + "swr": "^2.4.1", + "zustand": "^5.0.12" + }, + "devDependencies": { + "@tauri-apps/cli": "^2", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "eslint": "^9.39.4", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.5.2", + "jsdom": "^29.0.1", + "openapi-typescript": "^7.13.0", + "prettier": "^3.2.5", + "stylelint": "^16.2.1", + "stylelint-config-standard": "^36.0.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.58.0", + "vite": "^7.3.2", + "vitest": "^4.1.1" + }, + "engines": { + "node": ">=20.0.0", + "npm": ">=10.0.0" + } +} diff --git a/tauri-app/public/assets/logo.png b/tauri-app/public/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..78be1ffc65c041a48d2e8bfa0c3a837b99230455 GIT binary patch literal 26518 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 OCIcN)?b})oA^!v6jr!*R literal 0 HcmV?d00001 diff --git a/tauri-app/public/audio/presets/ai_huangyaoshi_712.mp3 b/tauri-app/public/audio/presets/ai_huangyaoshi_712.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..453fe535d87808028c8fc7b00698dda11a39106d GIT binary patch literal 54740 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 K|Ixpv0{;Q=EPqD; literal 0 HcmV?d00001 diff --git a/tauri-app/public/audio/presets/ai_taiwan_man2_speech02.mp3 b/tauri-app/public/audio/presets/ai_taiwan_man2_speech02.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..dec9e952966d68e789f588a311414219491cd2a7 GIT binary patch literal 35156 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

5m(P0Hs44!6rc?scz0zGVQ6W)i!mz2 z#4@74e{|+R+i&`4A_D8+a%$7p=KIZQZ@~ zx1l>Hz5JozQSF^$xT1@Z_-@ zXMnp_`KU5AY_M2HMcM6nIJb(^5!AJ7>bGMoI0;A&gRu_cM_LBZ{T=`i|KleL$E^KF z{TZhszK`68#aVV)No*!{RgPvhk}hJCfT!-XLq+h3OKZBsq|?mx%G3Pm88>tnmCmD+RL;Cog`!tEvtUy@vv zjC%Lw)h6qi0T@${x;0F>HRz} z8P1wMw8fA5Iq__-w?D;tZ}R%f74oT;kr72#Rh)-vis(tdh)&*1F-Anc=2THlR*S}5 z*+|c`G9~X&Oi~3(#E?0<9470ZQ)OLnzUB4z!IzG(ay<(*^Tp|+DsWu|DoT}KrK`EH zPSfwhK657(sMGE_EG3B9aQ#PU*dOsNUDWDrbq>G^6~S16Hds(X;F}` zHpaI}|G}(>eMsHnOSOvh=>Rm|zeD|Az((&zVQ~aHpDdQLU+}ak`5~CdFdx5{)x+c? zHM_Yefz|?5xT`YN_;gU$(U0o8w;$a~EG`t};)$8V59q3xLJhj&O78cUgx>u4$IlS1 z2>TI6+5B7WNzVc2Xlnj>;U%L%s4#nAXFVs-SUTHiAdrQ9=Up~!{2JxPM39Q+8|I>$ zn|>w92CCyc?s{G`tP_2aMgreR>f$ZxK)t%u87PU-E*^bSU0E_CRT48`-7OrCB!VNu zJRSfaCLqRFh`unopa9MQq{$njX1+*}yWo-QG7XbZH81Zuz%FV;##PdhR zO1*t&;7=NwoR=0<13rsGX}fK}&n&&l6x3w@%_E-P=%4_>||(gOTQ z!qr1A0<=&rbjD?Pc(^ZFj|Vsyz6Y@C(LlSu?+e4ZY}hEQ)@6YTH-i=2Ks|^HwCmm2 zqFQ)<+RUU%CLO*Ox!^>jmP0p-@YcZI{fh0CExAiN7ELEnw`U^qL95kX6K~u8{=9T( zUpnd>f^tkiUW_O+Os&-kOG|E~k5pQ`Yu%(UQ_f7YNgJ;F68OuYI;bSwB1S1ug!l7B zWzeJ60;q!CnA7yfkSUX#lx$_+$?7&&D;<_;3)6W{zUWODGWt{s&iNE{dN)y1q*On-0VBk?g9-nXCDPfNmzg8pwuL7vd!9jy6 zl|*evVWwE=iS)osdfN1QZ9wo3nA{_2x5e>}ar)S@R|k9LA2Kl$4R+ql zvJxV%`ludwu#Uc)T58jds(sVm*%x%s5aX4{%2Zl~K)ouG6a7tSxLNu5s&2#RVduj? zIfb$?$4B_wC?n!}iae2^7)qZ#Uur-MLF7re6&n{hxg>Qm60Tcfc-MGi0hZ4D8mQ(|@HPUOj7fiYlnTsJ&;f{v8!?bLUq^XR{y6U2IOi ztnEL3eneJA@H2>Arkll%U~pToZfb2R){K3gK*QncfYFcjrk$@2(|(z4#&FN|E|w3y z67!Sff1Y_RTvHM2o=xE{C+i&&v#16-y|;2yDA)e=ZZvJ@Z}BF(e!=VLhL#6q6(I(x?NIV>26^^wG`NoV8ZT3L-+&>@qU%&73EQs+|`Sb;(F;Y&7M0tp%SVEl^G*`L0H4|>CjS&_ zv@^(kRt;2&^Pm8i^6~i^2W%sQ*rsLTSd@z!4~*u)1%A@=2M?c)#23v7a`%&Tn(`7yOzX!ROl+>s?d;8H1}{|hBK zVxFpWKvr3W6znr?(F2x#O{;cGliu0=F zLa()26-GyJQ2=;c7Az(~@78 z42@sM-uW42qN)vd{4x`zUtyzH?&8As#ztq>U46B^T+5%hD(;XABA9imy?G&_GCIED z;xzw{pAR@LF@7eoGniV@$o_R@Ax{+N%(OUk^8E6X^BdMGYYTF(QH~$EBIXCW)<84k z$(rt`sLHoitrs&-8w&nAFzWx8BoEKTQy3cJQiObU0OXi(m@AQk0QU|Afk1*r7yt{1 z(Vzw38QIP7EKhO{Od~x<(=JHaAwOeF1kY!@HH$w2$0&j;O%2M=slO%|PQ9JdNg(SRvqKqOeMo;-~4;c~h-CNn+y42Egss8xB_V!Vs+9 zNLgPF2{60x*KPd0nej1TkX&B@deK z<7D#6+NOK=&J_lw-x4kV(Hqh$pkC#P;~*!)OGyIP$N<^+?EDBKib!L06?{v*RfH4Y zffJ5j6=KuIb`K0mg!Pp=b%w*;XawmxDnbJJ%|BTm{^REok9U_JOnH1$CY(QXTnQTHn)>EX6S2Rt?9`xGf1s( zR--tE;Yco*8f*M%JOF?6wX9a1))>lE(+1$aO?zBn^qt0m~|Lmspd~r#Ca}cI&-5~L`@nv%Nwo<0i z-bcx6)UvJ0BP|iIXOF3XTcyyQ>@kJ2`mScLV962Qtl{p8=30*xD9^pNq$DCX?I=|M zHFp{X6%Y`C8=0s~RBmWC+RcCwnWpN&HMqUG;rN-tG_X=?VjMk_K(kc<)MyaTbPizF zOzXMZz{>F4#*RvxqP?2<^NZ%|H^;$-spI8SoUy!4ekRUKQ)!t!H5BJ*L<@D zVSLjQ+((A?ax+huVuyv<`<@UUQZ*R8@@g@zuDkmH>ol_LEnz;b)PPMWE7HB+q_^+A znfIr5C*fc$pt<>+$;$WlqPZfsGG4Zs0k;=Bvk-A!TuDX43ezU%fN*szW)Y`x@LG$| zjuT45rU&8>kb{U2vMLkNu6a~i01RXHtQN`BmoVsT z1{E8G?zM`Gq*EG>kECYi6)-^%uT0CH$Gr;P9sbU#u$SWI|B&lW)-(l|$Q|xpf;MdH z!(lTlmaVPD=+d{yb!!wio9`{L@`tKz=G7OOtfQ3f*^aHftF?`o)X`W2yVoiCt*pEd zV=Oi=SGDMKZ-c7nx$*kC#+#ONONJtoMadOv4W40o+(}8XQYsIpr~`;ts=G&OQX2#6bbf6a*tGD$y#|aS;dvA0<8^fEIwmxuZrR z-f^-p0cM{;X)dwO>p&MkOF9Puts32+<-)tb{m1K+UYz4k>TERvZo1(D*jEWEpJQH7 z$feg}=&pL8?j~ zg&9M`1D=)vE7qTlG4HXSRXdQxi0Ku`${TPx{60q=K$tC1bmh zS69m;!}n$5)~o&arBxgrN1HK3mFk^o2RiW%M^@CAFwXts=QEDI^LGA%u0U1)8%t)-RHZm}*og z&HRp;;@P@@_b?2gD!t_a{H1@SYu z{i@m<&U{@HI|eR<3ws=Jg$}L~ID1s+nLP@7PIIXfbyg!I7u*uK|1m@1p+4+MoqGjt zbXKzaxX51qs~fj5zg*VW6y~OP4olP~`0&rK{U`16b#mAohD^UxojqmCpptUEj@ZtU zY`uVinat0Q!jZ}d3o*z9zAa(6xF)icV+;pU69hQ;QxrmjT7XQrA_(F;u^zbyYN%Qg zKx!cZ$~_BH^KcOH-2UnXb`7$*;Usd`wehoP;|hrr<276EYT9C(su`u$!B|sDVKP7V zQ`yO4sg<4w*LhSfEe#cnQRdhm?je;(GnhDY3SrsP+NpJlE#-(m9~rn2(L99Q)6!uP zYYd$)Pb^@8uB#)jF2wo_F2;ymgUX!rITbbYEQ+PxCsO|7=K{~Mlb@3EEPjM-eu~oG*qf)-_r7dc@IutT^Vh(lbk&Dp=gL`ozgXkz>wuS8ma?PF)vY1BA zAWCfylUGX8IUkQGHlqd7QgXZgj<2{a&B>0vmTY+liXi(Na)>>y_p+a>z1eI~>Xa2; z$vICU6Zopx7Rz>{Z7=ftmqM`RXu&AEj8NAU9ig>N`;H4`Hkqe^|kB^YSPP(0M7 z(gkf%PoT9~E*a%1Pn}L;f;sh0vP>PvG%H-0!&sIQC*`t8Ob7Q4+IlUu56Dg4a> zZG4=W1sIQC~*kfsJs}~V3 zNJ*UMoqmLNK3ildX%RKbeHYdm-|{)LI3DvYLVewVX7;^~{y%=M@Ele7!D2_LQMdCK z{aR}&7A|NsyR=e0&ycmE)EDh>ukCq_H+~#T(Ktg3#h)GhJ_AL*=`oF}e|MLcif5dRntGFeeRFY$&{Px?&lG3kE&;dk@nF8X+GVH5 zYx`t3;?LD+hNNesAOEcstZEk3vP+vHCYGgRTEoWEOT z)%EkE1?qEaq%MyV4@2%!jZ3}$%5$MBw!CN}rM^7(<`I9tkIV9@DeoF;^`;6m#jqAi zO@$?s?{w!m-rYSc>oF5}E`E@pew~oB(0VUkk{@eQNJ~3Eu z(nc{LzIQ41EeH?+jg`L>fi>a@tAQ7#!|8wAor7+bh`w*<^V^BYf&Aqi$WSc~WOA`Bt>k z=5BdYi1np08g=AOzI^nYuj|d3=i~NPpQ;a!^o4HMh?n`+vs@_|qQXsSl@|t#eT7-R zPZ9xW&WLeoMnI&P;8YJV`U4-hnm=lII3%1SSfJv7V`!#DBTr&Ep~AHI@nfa4Nz}wk z3c4r$Rhh%4t=1ri_NUA(ZuOdf|G17&DZ&6MNhutcM%_|ddCQv#koFLj9v-K|JBo}c zo@Zn)O@yVbyaf`fDypLgIeqt^GbBbV?d+O=@MW)-&()i4C%Mr zxVx88De^$DX`~}nuHT|hKVI70-QU?l);rneMTy6zPwT!1Wll}mpG0dlh}Xlc6v>$U zD+#rHk!Bcs<^(%B4HLeWud#@BXoR2~>CDE28R9AojPNd>kDv)6n_zn!A{zg;L=eX~ z4Uy-p|Np>>K+*j{cR`C)X{@lg-zsoPNbwTL7J>sJ`# zI!?=|X~Af}1e=>5UHtJ|T}noLlIJ+8T{GH+Je{pC00DB=ZIUMk7gh>OcSRnvbBm)x z+qI=M%zd>6rFh4ZAv|Zoe53rdLwd1qJYU8IpFUvgqwoN^1X(7&{PLgk*Pb6g_}M`^ z5;Ahj543BCTDs~CBc(6B>sZlQCqWRH-5Fnta$_hs%&bMOv!GwdZxulEKfFa1bcAYY ze+&w#SvUeXfBt0mIrA&|wkh=z1(Zx}7{s9(`(_Xa0rUPwtU~1a?yE%bAl0JK@#MUX zkeSQ;hNwG`3GS5s@Hsvp2)(OptTF%{n3`*6p^#*jL9osXJx!-=%WgC`?0n@IO{tE3 z`pPdfS_NfRN7rJz5bcC;e?cyyrq?o>^y<=}+oH_Y+G30P1+!a+dRF_h_l>Emu7Y$0 zX9h`zsPFdBhPQdkCmja#RN11Jy!O?OnsN3F@3u88YWLKK3(36)2D#?22`_*_uC?8H z^mWxAerx&|qsvLYp2Zg$n96JPC&Rt*H>Cw_mRY9iK5*(SQc&JEQ1T5F4&CKxoJT1& z%6Q*3bTXJ4cpsp8P`oEEeG*$=RqNj@S=4FLBmouJ_qZR>lhoQ2v`5ej%}hDmTJM%5 zW0Y{Ef;xdLhJnELtC)8m>FWkn&}3%pZ4^SA;OX5(&8J!!mC^IQt% zRuzk+Sr6!DuE=Ns&Fp`#6A;Yz%+TQxz01SPKGG4x>guBq|2cVCcaDUY3*DKa-(IUj zctkIiEFRk;f55{-pc(Jel{c82)QP3#{aP+sCCFuUc%W*{3H|c&A3t9r_f`2R|Gj_N zjO+~F^3(C~c8W7J%s2SaJ(4q*`qZlQqWWQ4ZQ22sx{r|HG(~gAE1XWCC!4U6#-+gZ zigfSC4z^u+-{_LNLi)bqp1+^|u*#SU@!fAhcBA`vf`BQZroS25=)W z6;JvAoRC@V7N-EiOssOA1^?-wGCYx@sC!>^5>QT!12PZ}F^yO>CKC@dYH>ajqQXX2 z5P0!vDR8jX3umU1P15cTf3<`xYbo8igG$8g7(cUUx&-<2WG}zG43G1)gozgnlhDUT zMW1Bk&g{_8Qfhi-xY9A-5!u5tCc#=|s`gIi);fg#B%4mCCH7%VeV5?u5Lfi`SPI$M zvfn4CvNAjL&OHS3Gp0^x#DiQj_s8Pt4-@8Doj%oUKHZ`Qk>7+3iaNGz@7JEE5DQ0d zc#4$q_1Gwdnd{36qu|(1$lg-=5>w!W&h;zK$>X+Bw=KP3pYZB)zlae; z@~Lh#m?6&$HLOak@_M#0RT(VNsOOLI;E1B^H_xiu)KNI8jN{ZKN)q4BTCu+7|!?5F4=Zju;E42I}tO zgA&-X=jST;r4M~3jWb(BvK;)%bzn1A_E3#Qr}Z9Nhs{?B#d>yS%et+{ga;e19=43# zn04x{qQb;lJRfMZdXPML_E%Tne6N{TJ*ab5JQ=-YAg^=R8R<9I_>Z5naL(%6@y9KC zgaNt7`?3WAzk9ujO_vEOaGx=MVJ90zT+=lFtDvp!#V?Ou{6w>s2MhzQ#gQhB*3HNV z_YC1JBRU+;M0Y~4S~Pe|W@9vjd91_?c$AUm@nR#W!O{r1u6jFq5Tsqs9&1?)Ju1aV zDVXt%jpvCc;tA+8jjLlXyJqpf|Lu6y=Pg0uwp6&BdeQ*U+A7rVvl4Y0dxd_Rrh{49 zV66M&(gcSd>D~79y(0(?C8%kULS{JFM+O9MD={ogx;R*hKrNlZXcB!$8Y5kZuL;Yo zolNdBF+a^@8aCapRF6u~t#6^z;0<(BDZ|1|hgZhuxxdcoq_tO}c@Ed>_{ZJu!6ek| zOvGsQbBcEkrP;RK+{j-!7%ciF)|w-19xeuF=%GrTb%j;!8B;D2x!@`eZh%&vnoiI@9qeCXRgpvmi^=B2M*hF`~Su5 z{#6^~&dR%%uDgepTfgO`A9hnuOIgL}7}_W~`vj=dkjBWAy~lh9n^t{IXRY9cG_u(Q zAC_)JGpSeeW>-lSjAtw}W#}2Er7XMo1lc^o!!y|-{;U616F9a4;lSH?L#QfF!eAXN zkTyfRL%()%T1Aos;n6dTj5**~z&Vpu+VVQ55Q%1q-C_&&)$)W;Ny*VzCY(!tDm@)uiC}>85{h5)p^IIW9BhtX;t;p{IJ+>rByf9!>GqO z6>q2umfclMnrc=p1#-1fT^c@4LfjT4@zbaxX8TEUMhf}NFV{R@I%NG4)LElUH7b1G zc2FSMzgYkub3i`2p_8hyCW(u}9Rk7ww2Y;{!pagxcttlbmb%=Pm8WD$qW!^cjduNt zkJZJWCcHkDH&On(!_qBKRFrIrbU57mMfbhv#X-gfD}Pl+NPU=mo0_2tr03V7-^fUb z8hGW6UTFJrOz~=pT6;Q)e&JQnNBYfvrslE+82gfI{e*;hRS91o;~;hvS1>$ENWB#I;})gbwu`vRdMrq@aT* z%SRG^@K#TPDn^Y3s75%rB4j*Fh<8P(Q|k8FFT-rjnj-%Zy?hfGerWcOpVM%@Iezkv z835sxTDhg-0zibonJS!vgA>ZC1{{D2P&#K!VZs%567htrnpHU@2aIfl*6lxH8)qkM z-aX6Sf;v%%ZR|OqjOeUrV@rO0z>5W>#T(}_6A}Z`27I*G1&D~k1u5lTtUKpVy7(7I zaxp%)D}S;V3gy8Q*~}V#Qtq==(?Kati$73Gtb@{0NS(J;h_OmKl#&MCdN>Mv<21)YDOO%gLzJF+e!CG7A)| z@1Ol%+suJ7X`~EVcp1AiqMP>Rk9KVvlji6ECveH@vFb+)>J%AfS1}dB{<+HPlM4V~ z1cwS&5di^$0dzEY%s`rjrk_>~gjem^fn3<w=cj$_=31$(wGWDWeF;vFY09+Az=z?Z^WJ_Lw8kjiIr~H z+^TiX52dB}Hmc{Phaq_@ySJ|vv$?ylP>QnOH|oEYd>}dGxPnyh44s-#Flc0?9+(qD zJ35u)_9nRcSl4Iz_+5YUL}S^FX-5^|2*#M0Szh_ap4N^$oX=#4zOS;Bsb`$|i{!!h z!B^Mb`Hwd@pPr<&#=42!6~B6QS*`m<$SRTZs$bKFo;k0ex}WQNmXLeNovfv;s=_vP z(2MNb#cWSE{yR_d3v+J&|DIz$nD4$9qESVot-lfMc9T0Mq}=8+=4WF)dGe2+IG~Uc zKY#HifWR2J&wJL_h2PYx__6=Y0Fn*Jm25PbY-- z3)pk!P1T)G?4G~8%K7Ng8Pdxj>Y}JC@I3AY|l>YI+Nebb7)A+6LqV zK-LlKJUzj16cj!~0%#wsv?nJA7!80(W*SlB_A{dj2h;%I9uidr-Tq)VJfP;s&}zrS zYT*KVD6xDyLQ+J8PcKb!8~X~{7Scw1c>nF#Imjx!^|!dmoK&PW3xl~a^@1<;ZvCrt z^VN-B+AQk2-opO6QYEi+2LGHTLWA+NA*+^2zbWeP#>x9W?-{p61j!>P#S9m&Z7U;Z zRaZNne!H}Be=r_45@{1?M!pSMK~n$lqU_T0;#&E7+&R?fW8}d5wnHTOYOcz~t=Ogl z75cCuEO9M0?OI|}mmvD@`}((Qq?*zPQU!T4!SO^=Z#Yom2^q(CXFRewiFiK9>2XGF zKdsOOmZ~8G@(Xk`G@hCixofV3kRS`};X;}I>Mv)(8Nr6AlzgGjDY!(oo~7TJ{9E{8NM@TET1CKnLbj3 zIbn>EDVAS|pYV=zX$D$ldH%}m$I+nSm;w)WEN&^AOtO@sy{JZla{ZIA2={NyW#2% zF0Q9JzgKqrDtU|%r+a1$&dd2PJiUW=T<`n+J@Lf0ZA|ROw%yn^+nE@XG`1Vtwi>6g z)3`Ak7Ww7<`L5?V>--6?b=~LO``UXi{+OI|nY^0Ym}_f3`nNQ@+-c{yT;Bi#*fp1c zePQ+2pyI(&MFA*|Nzsv`!o|W5P(>(D3)$3yrg@fv-aX1myGqw!JA61Z{&8Y%+WB ztSmqT33y7hMRF>j2^1B@XrG)U)G~np z{^N%s>#cc9txG*#^C6!=jNV_Tb{kyfo$sf0BImp(n{>Q{z8d z&X(?Uw4@yjjY+83Ul+UbT&|^xBZ6^B>W!LeX|+oH^V&Jv6gylmazcYqo~9c}X8@c+ zh?h5X;pnaXSo0Goi45yguq;}-P-Ei~nA5NPtb3*J)4a&T!;{BjIT59JkJL zJ?3uh?6RonvGF{e)&20H-SNC{Bw)7g>!R6o&qJV}u&oz``;wdhc$bQGbgh~<~kwe!a@gfz|&a40KTArqYz=xHBIXS z!nJ&>;uYZ|c!AQ0L*~a)zqrp;()8q(7ln18oo}uj$;Y2E-VZM@EEoxv>k##^ta_qC zSfz~AB8f3#-)da=P15Pq_X=QhPEKu8wKbTZp-tb;6fx{`Dq_nhz#(^YabqMJCHs$B zS8S%{PfeKS*z9UqqK|VE+nGx56qR2fThICz8);2>N5e^@_5fRsSWB4Xn{ z6{g8^wYllw<*R0klyP?Vfq~3?adE!1zFMw^H$Y>^uFbuVE)|)dWeR#vguxmq^5%f^ z(CCtriiZ`VE~OB}m*H|n8vb>{mb_v}bR$kJBuRC%5nt_m`Mcf7`}Fm3H3mN`tTFP` zcr)aWXZovFobbz%W&f|bvB7XTzfu)Urp8H-qNi7*$XnEG!GJju0i4u;nlWd})gQ)x zqhu>W3C+KQv|P7O<(S*EQlf;psMm-xmoh-Wns;LgKxGK9h=MxnmLrsui6``Kl!b|S zPME`jB!TLRQiKsh=M9Z@Su0BN@FVs3z>0zsV6d$$T{!Z#5sDxIiD&7}fnk1`^K!Mi zG6QW*%j%-DQ8IAKfCzytW^(0z&KGO$!0iujPAEM`S@BKREx~@5MNE|wcZ{r(*=qBL zln?d7t4Vuh8Lh`=%#J6MmT9hX1y%mD{LPJK585dNrNU|72DvV1aiJL;V%3b*q?$UD z^|GJwHkNBHy=Jnkg$#0&t$Iu)afnn=aeRVuV{zy4XVlE}LF@`QXR1o>#1L8>jf7la zUWa+G=)C61ffe1zF<}v@&dK3!u2E-Fa&z&rHb*P6hG1X6LHdJ>Lur|9Yu&1!i|h2) zRuYs=I8;O=M0ebMiG6V*M3(GZ#q2fe@)8wmDGYSO?luK@B3GJn1 z>KtW@!Zx!8TeML7m`EnF_?Tx66Dapa&1j|-eO(N@5+e&277gvBthoadAg3P3_-?8x z#=Gg#9ty%RKdZ2Wp^895fNy!~;)CaL6c#P+ttOgnBmb8B_wCDV_vYXI#KN6K>VawW z>&J?vd;P#A!yT!}305|y#I!d3YOV1#bwj9LP=7@E?d}KvQc z2Ta1`g}rM|XU4}w-DMqp`xKueUFJl{agk?HX zhNdBm{u-tMmDjot>={xbAj**=8_oFxiZm_3o=LKV7U?iEE$PT91_8l zZwmphYepKF^MIj^evlvb^KHw2b)I!TA9!Rs{OMH059b?GbO`93w`=Sq!a4?eU`Z|Z zGBL7%+5h8*8k{eC%UjE}H6tQ(oL1B4e~8KHMxW5@5L{%;pff>UNn~l_qhMx}0_qq+ z#dbMx3q95VdTco*-(YEo*bm&LjS~#D-t%;0mf}sx$njBswHh3fAP~t)*(st{J&)tN zZ{|`#%Fc^E7X+o!HQGC zMUx!-E_SqFcSrDu%Ms1-_)ABx=@m9dQn-@V7{NOcu#g0Tw~;s{dUeEU@H4l=}BUL-b_>5|KPEN&~|kI7b}w?--$aPQoZUp8d$2+ty{6k zY9}l?a4xVIHIEcsSA4|-KXRKlDr&4qeWui|r^{oUOM7@`7;in-YR{zCS zF$r7uPcyu-K^@2sCiM?Qw1`yiznK7RqY4@b3Z7T4O+HhAP&!(}VcdD`l=wX%sOX!= z_Rgq*t5PGu|D9jkd~dSfdU>Y&`k1~yl?4pxXji4Csfc%<{<^KAiO5IRLT>d~s~0Ke z?$+TZ8Er(ACt#>Od5sAG43B*$V{ah)a4rr*f5f zP3x_T(UvZn-Y*S-sU>R$_?;?*ykVdf&F>FvXy>eljVhdLt8DAq0wX;{(9 zpbt-r(tgvg`Jn&!Q2=Zs+{;15c&1{+?aiN>1lJ56DC+QL<^?=8P^jcWbwWI)ZnJo@ zw0S>C^6}1sENh5n z6Tg~0wt7^UH}?y#e6%b*ciXNNZawvn*5R|9ur&x}K?5)WkP(%sd?u-xc(zi|c^L@U zDqYbA2i7B?c+>%9v>+`vr+bRzN@I%RN&x`vpXC=db{dR$<*kNB0Q3`pO1tF_7A9GF z$7ri`&NQY}+BTN0NsA233smNfyi}mqtXvyWHApXJmY$C~Ktr`qdjxKn+?*xQQgRR* z9$NcI{+dusk+Q>P5H8HZe=dr=w$@?BgrpTNy0hc#r+tja8RO64s+jEwEo_Bp?Fd zuu-j2-G@1;2MC1!1s*vQ zp8fb1WEVFj8o(~!DyA+XC5Q(^w`d%B)LX5K2irfcJr1@Mk^4mqlPvYhOv#8x+1&=<$&No$}Q&utzD-WVjNR62;k3-ddI5 z((*mx1<+wF(N&@u13IvA;Ahe%j;W2!{TuLNEw%J%HXtiA2#@+)K5_E}q-XoT6m1|B z)a2dAUNM)o9EoO>*&$Y>Vfq-UjO8q?LCRl1wgudA7K*`ir<^{WGIDnN&H9y|u3g;G z*W|P>ygop?=uwmp<=JDG$JZ^1t6l8{v1P3xjxOzXJRTtS`jcWxytUVmt%voMb-DRT zI!ml*xb)JS-v*xX5Az?g&lKpMjhypzqm+Hz2L>vgKj2NW^?$&utMa_^ZNYnb+cV7Y zGMF5KRCDW#&TV3Yzx2C>oVZNFV3_)6-uZ+RHNFINq!1Wfs-6_}5bh^Ru2B_4=5#c- z3vFIiSJt}3RdmQ?|Hn@iI9KdN2)#EjEd77}cZ&eLlP3`+*P;}@W7&(bDC2<~@muP# zwu0uq*I%YB7YMuv!&VA>vmp!x`0?z@KHpg$3J2W z$5dmG2z|U;o8g_{Cecwk+`VzA2m|iFmO3j|M+z<^0!)7Hsr>Z2tJ)6O*7epdqx@v8 zQ3P3fcACDfNlg@MvzP=J(!vm+(S8%uO*?xV@d9Uk7nGuCk7F@Z`a;tF4{5S9sgATV zF0c+`OMQfW=VLL_Yd>b2Y0rw~5fPT{%ydQ7Gz}*i zmA^>?6DmTDMU{|rvx{6Q{P6Gq0XS$f^`Y2{A0@3EdfP@qds{xk7*4Ud9IKpMm&dxF z(Y@;fE>U({L{I7FKb9J6S(hm(85k-^B$6u4&~GXv65D4C28QlDj7%mp_dOy(!NH?s zDu6;JM%5M6@E;BGc=IbCh7Mu-hISc$_;vO=WqI--=UBbzbbIxtFnv-={KY8(BV^ILHxHt{|F{RKkwv%gRm8 ztQP|fM1AwA-WtY{zdI=W$IlkPbYht|zE^ZaSA>3Vy061mT7t?ZiKD75iXgl4E`OmF zGSD_TyL0#9ON15aTJ57lRwGHSGHp_nupvtDlQy@MX)Ve0O%_d`QB2jCXKO1#3L-{q z7l*r7!M|(({Lp*84j#yt_H3GomAk%%1f6XuPf1omPL?GUJY$>S9&W)FMqx)XE=0NE zq)LqH>{UP#~f9Q}NqfXyf4Nn>a`J`-CtauZ5 zX|CHz#s;SJ1MLq!FT?kati8@(gJ=926)AQ_LVwltnxsZO&~(g{T6y^}SUJS=abmum z!WxgS)kY!0CNqN^!E@+ZY$$R|-Bit}%EZCcr9JGjM@D|^sel4KC>9vjpU>)<&>T+g zxh_E`nJqW5i>*p3EB`>{+I}_C{^53SwAW14 zog_onp6WWt5sa2?CWW{TQsE{LZ@XFE8yL87H8ba8?wOJ>-*origAfB0G#-SWl>k!o z_SNz%Rubla3Np=~d$X=llY@wKwnq3og%A<}qv#gl-70%#<_yYes2LIsPD?6szAJ>? zr^F2W$s#af=BwIb84@Y;4C=~#S5XBI6*_&k2d!wJj#hpy6NA1ar|%>2|IE3p^<)Wi zARMlJ?yGEzBX0e;+JtV#!%sE4J4a?>t1QyC(ky<)|9(nrpoBgNBvkLmgobBq#AFrX zNii*>d{)9bhbUw@oQ~)ycK*lDb*TB^Dkq1P6QNKggRl`UFj_IV&<59+ zKsw2+a{Z<%{1ke~-Y!Xj7U^%_-yKV!TW(%6JzeE_t*g05lY@76h0=$IGW&)D2gtmy zw};+bKF+MU!h&JI=Ff>r547tLNsn50&1{Jx@GeU;8=w%!worx*i&s#kr-B3*3G+Ur zf=+|fGk|@}IULZRP9FM$<5PW)NdN4ja8ua?%oe_@OSp*BAmJi3l)J;CHHCVBdtaYk zf9CIJB|sNw&9lin6%J~{6gRN)p}^ORa}HlKVpf@bNh;%?qAQUhN}n#mu=}EWaLry& zr-gDFgGQ8AvOHUMxJmo;`J2d-WkCc!Jd&-Oc4m80wU-*~Po)>{K!d-b@R&$83ycr7 zI7r}Al6!nN6C&jJqO&K-KwF~`+%bl;*l=2M_EFa^`%IIpu<8oLQAiCo96+MjL?OJd zkvOko1dppp^WiJLS;J&Yi1_r?&kJ=UJi`(1pWexcQQ0Cw2cry8j-?go z#lfXfErfY&q`uo6aG{dyXL;Y`VOyVK$wEtHCNH}^SPb~du3oy@A9@&cK-oedeHf)8 zuT;?M3=2_;!kMac+e>`*9qMYU%~0M@@}I`8#gOdHc&~y8|F5Uy1%5UyzU+@Wzn2?b zj=oy`jp@~p1TxrP^<2cPn4=Hzd6ceI zXF3%}fXEShs*p4}k^fcO*Qpoqa1eg;I>zH?T`am^d`L?OtmE{xzH4k9oYhY)V@3GK zKxBI!2_7YX^8o<qA-5khtq3g7Qe0L`sWrYCHnt_a*$N<3DQ5ZKW zVi-zYmv%hw`gH9Egror*6}$6PLYBsNVcCO<(YLxj>N9a{YEYmC6MHiCFo<=8jyGPE zZZ?G0ET(_4IWS&-cA4E?rwt{kw{AbI|8|`m9$T7o@^bPnRynmFbBa@Wo27O~8nqLq zlXdVhe3T-cl*neTCK?5Sn<+?NpYwpj!R1KQk#6K`0-fp9#|!K(jxh;!M2bxkLKYu0 zl9O-p8d{Eu3G`A$$Sze4r=lvl4w?Q^+VqPG9?z58Fty4&X!`2Q!nWNZ=2k%&i#%`Y z^jI{Xbpr<~b`eFex^GMGXyJ&2RUE!+4OK@`mmo1kM{@L{zfgyR2h2$BP&ta2$gz4L<)9{3ke4usftzv%5yA1;M=XWu~t9eAij^WBVsm&QR4$BZ(xQ zA}9Wfp>IWqegmeih)t$V%)k8U|KrDY;(?jN;y2tUTrjwK{?0}!O}E$l!!&Jd zZDK2WGG{YucBjrT_il~8fZFix2IeeleZ{i#bRz}Dc!leEw>U)9;c!XbC@WtM{mrFs zrJRZ5m_jK<{jU-L@cyD{qpN@biU27MJQ)(AVGCj)GfHUHm=prSkQWL`GrD9G77pAe zorci{-oXaq9~phM;2|p2$%F-IrZX4>>Ma`X=phZGS$UWyK67`P2}2i)BF2|A6T_Wl&1-G|zBSCFH>BRf`c>>NL_jIs9}{68822Fys^UX^dKx}~3{ zXp;gjJxi)kZ0%w6u@&(B8H+zKc58bS1SMCRDz)-V`d(P-lE#kOQ695gYW8#gT&X^A z#HWZINX1|iVeRKA?kI+GkcTF^?_tT|(+~wV$<%rv#*?GI$AWAJAF@dR;L#Js2^bk! z9J(X<(#Z`Z%(0BV8vzQ1GNXo8EU4fJV(2=7^{@M?q0Kg`E_$ao~v2amomF3Li%=;TVl+dIoiZw-z@Fl z`c_{2oddd;lNcp;ue+s#p>aMPTBAN@C4Sat!kL10vj)=^v6du2Y%(K-$6J#nHwsuL zy$TZoB~SaQ3xkLi8af!p$3=$$3UwC2qS>iz+0+J&z%VaE|MX8C4jL?$;fv_$2n)d( zPLXAgvR9cdB1q6`{Z4JdhO0173Jv@Wp~XYQ!1c#y*v0^A^=x_WVKRdR zeABW7coXu}?dZu=-rXyBZPYZmwtVexGxdr=%9#dut9*MhI#31EM4+Xfutx{9Rw;ku zjXd?k&BaJl57Zn5YiC}as}Nky({$d5H<KS_70#(8eB{ZyU37J1@Y zbuw$(E=0}%MPfs(CB1HL-c`f|zXo&jXkiR^y=ZC+Qm3H=*W+UQ%`#(crC$(=>=zi7_2*Y$R+hs39OYZLcJeV@pQ}AfE z9I42HW#ge1k+gE7GxN?!(r9B?LWzR-M;dY`=7Uswf`L4A7G+ozW=M~~Iwl;OFvpLy zK|wsk-;9@a%WfpRzQ1NeD`UZ3*Gsz3+WwXg4#6G=P=!+gW;5a;VbJH zjNVif2(@93f&#+UQdV#iQ9;p?)~DW%!09G|4Z#_IaeyA8$sfU|98xQ@6t|gJ`{VP) zG4rg~Lx?md7Zq+g5qb(vZwdDg;R+q|Ev*arx0I>g)I{U&t`cCFNLsBiuboB-po~Kv z9xiZz@9kg8<~Ou&}xdeFyU zVo!Gx>j`o}snO5ru^#M1aaa8~;g|$2OwrE~@Im}?RqEz&)!!cf&7V}r?$HZF-QKlySvNh*7N|=w%U;TI{xxoN zBHaYcrhLAcs%01=cC(aKn@ARbl8ew<#16nW>G!D$77EkNZg^#fpY^Wl;C}puHm9)N z2{+a-JJR-u`P;G1IRKQ&7IF4)u{UlF1?E)pFmp%;aI@I3Y$`Z7AcxAH5r5SS? zTC-j!3aN@X`jzahUb?hzRtDZ040Oa?QBp$qwDK~gCQdG@V3m=lzWA{9+7zyye$INk zh(!-saXi|(tNoN!YvZuov+3s0LG(&XubrNTHL8bW1fR4 zqh5m^3vXG{Br-_8q>vvG0~!@E@`7Qy&;-%C3 zzFXVx40~ckn8~mbCE=(D4LQO>B}CYrSnD+4k#)5Hktjd*hpAgA6hd&m%Vv2>)7tL> zs3*egS?tl^7e$u=Q#zadJZK5o2bMiJCH@A$@9yxEk?Fz?J(C7pfN5+P6s$P@=t$(O zqL{I-=&faJWPE_xHBXHWNsD7qaI@^;w5(Kw1S%dom*9pjT)wCS(CE%`+_lCz$+T~L z!0>>9_=F@o>}-RDT2cZ$AffDy!eAawHJE}(hBcaEEenimXs#p~(6I1{i4;c$m`@T( zGSF3yawpqjtx>~(@WVf6NCv^eGh2Leqbhb}S|DnDny`p#U?GD=u&6GN)#onjYMU1| z$F0emZlw8mVxNe`#X_!`7tDimuTl($ZF(oG$AChHu3CX>(7QM5N+d}MvnD>St&xv| z+rnovL1m0osB-ZswFnMomszp|zY6D2RW^@KjcDa=C8}TnOP{qI_OC>t^hSa&d~wp9 zm_}5_@?vmQc#gIpA9=kFvIDlKytz-@kwm@okgQyXp1|Z4J_)@c0Q5g`ir-f4Z2ooA zUMy`^>qnenqh3q?^7it|saQ_IrM|*VG4DVcRzy8q;sqenC5h=fPQh0*o1ZolHAMvb zYr@~3t`mQLv*IsZ3*6ac#pZ?e;3NmK6K%mGnN{<}fs*n(!p zFlg>l%$`E-{t(Ug+~zB;UdN8fqp|T4uViAGE+rZ=3J(P*aKj{I{MHf%zC=N&a^4aC zmX5^?f3~D?_yn;dxFlB$Yx~4#eQj zjBqpS@~0IN0TOho_rn}rYi3jF*S;ln%O;<;8H|7Kna%3O!Rhs&NafpAWwIPRk4ZCX zf8*8laPz;YvI$xWc!j8gShTCr=SUNntGeZtE`moustt*KS9?Db>`s2}sR@RPDzRB`c zq{19Eb^0&8bejhH`%NpOcioXrYtBJdgpPyG^$uc|wh?uodsltvO}|mu60LZlX?+ms zKBZ|^!*24PPkdE%)&yCXra4JVN?^UOGLw>y=eg=Ex(Bf+(+M3Y#$VLkW7bdq-d2^G zCQ+<)RgqTQ#;vHqTIBPuP9{^D92@D<9du=r?Yi19VdyI9E~uh*WBj^Ml5g+S);jzX zewxe@&maAQ_5CK}LOLPxU?MMq8hJ>>DvbLy&msTG%_6*000+4wMiwgtr^&re5W{zw zbhTcK=QJ(Wh{vWL8#Tg+O6ArXHl_wGu@7+a9c_OHn5k17+oJ|#C_yWXTcW1b#KRIs z9trLHq(ChZWZa4$%9Oc`%&&X6o;CcyG)oYh@gF}f;BEFN<>`0*EA2z2|5KAc#kSiE zt6IPBqBDhG7i={{Awva#e@3U%-HJgfc@fuhd)W`$=oa?L<>QNkJwY|Rvs7GILqnDE zoQx5RRU_NjUC_BZCsrp^D%h(SKP#^7uT*rU5Fy=zK~&v3(MpcVww!H>ebjnP8GY>; zcL}&4)__OWJ#cNmGm5y+E;|n8d)jjB%ErgJVQ6l0a#AbH|AIP`Q?D89S-obZWuFYWA;KqJ+U>mKdf~g{G=q!z6E%tw zf-1G{zRz$grUA`$O_g4GKJ`93{l>LCJXhh#>^^ZyVk7`6j0i}|DDuoHn8_g^-{;+D zp6FLvPv~J{Iz_5QrB7gTN^30Y-yZ!rG#9~(nCxTNb7I8M`kiHC(UI_}!v`3XFTv&S zO(QbTC_O~hX)~JWZLBRuy*lx@yJ^&OhTl^m`W@CwNC-S@SR5(Iz=Ci4y0*%2@tZV_+^7F@oSM+t}=!R+X&!*N+~mwhRD1>%a{GQMQ&o5Mh4_XkMz z_;1l7=seH;ylkApbWe>8Au{wD#iZ)}9Gd{$yKTS!_{jkmus=w|i^UIX2>;w0EsrK6 zuEzQ7xE7kPm)CVKu#hH^;t<#5`xnxkb`?_XgoB@QnfgoG$35q_?@#VL;$kP0_CnH;MVd2!9Zl12? z>vvbPDncf!SJlKQ%dOI9mRVX>-aTmGxRjo64O$TDm)I356%?XSmTrVGvDj~lxcms1 z5>!Oop&hd+J=;YsOf2CK(W`lM*sQyq-yHy<^1rf5Fl_S3h7sY%kxRa5G1)H?i=f$` zgl_`1#xpEW?O2bqmi{g3cw|&J^Oq~sRBr|y(AlA$@*?1{Tjl5x)ts8#C|PWvAfloV z^4WaZ!uZa}Cse@R%!puLf*l~6IzbApgots8q&Sw_+wkX&zq<*%>T`dMXtgpL*wL-f z@^Wtf9ko9xI?wI&%}MpXnf}1;XM1+%%cHf7*5pYN#bjGQy&zjwGQZ)Q;xUoXWqF+{hLbAHMv+ET`%|x`FXuKu5HJ^5BGS%TAJ4&>Em6!ib{f5oHYZjJw#;`| zFeiD}V=3{-)1*>o1S}~7r-p=Mmj=)9+=6XQtnxEio4aRFkY1rCZ%R4)EGNyVQb1AY zk3#mMeMAH?wIR$>HrR-v1f-NY@z1@R+iJcy!UsR_yARwqz4my-2HuH(_{=S_o#=LU z0U}THcAuS$lBx;tl4{mECCbxuO3%BSdM{>w*kP@E*_R3)@`EBfHekuA!0W)%g^DLRc|M+=>DNUf{K(|PndXTog%_wWc(=Jc) z7Hr6bl*LCz8zIQ-^Rgy$SDZd0M8~5~2r;bL_yt0PYmGXZg}sNsFaTr#-0-_greml= zn_UX}hXh2VV<3Y^Lc%=YbO4HlcQku%&BOzt^Qr5)0{~zM47(zWeI|eilguLfna%S) z)E$&6x7;^uM+_Cii;>WJvIKhWW*+pNOZjznTK(H)M{wrY_y9_NV~b3lcD(7jVu~Uw649m*8AbNvaAO#$dmHy*4cPvmGJ(Q}g-6xrx9EGx4 zPN?pnv57@r(k&A3Dl{4|VY#(2i7l44o<5!)>MBE1zNH(Lrzv&OU9@#3jMTX~i&B*o zH1i058tdz+O|pq^nN}4QT6sSI`l(}(+R4u&gIT8qfUybp+8dNGj;=K^y;K3@H zC?s4I1DDd$sIb#0u*&dfe1%+Gc+YhW_5iNJutYl3ZlYv4s5WL0@!BA4JvQ>#?2tzEY&EShX;Qy8dXjzfJ> z(`d;Zd3&~5qi*Pa94O5#y9T#O*cMW5D|Jcv!?)G8?rrw%jcDxj58IF7$f_7#<2SmH zDZkAaI{C&Qt4Luedurdi-D+-rkFRJqay8Q}{*NCp1N(1w44cbo-rlmJ8L=Dfp){Sb zAH*~NjC2`gNVBcUl6a^%loC=wisa(Yf0J(uX@2MhXhZW^mWZ8F3{VE0fJn?! zQTq_LDy#vJmo=xtvcB)(nmhnkjj2Bsz){er9H|GJ1I-mwUm|4&uY-Q8B}T>z3a-Og zG5CM)5~7)Q2QF$jO-1|5@Sf4%Izo$Z#mS>uTgf>p3h0cqryP!#KOJ1@DGBcN^cG?q z=zFP zY*ObKMq67JIEL{&^+)VQ7gZ{VbY&^7R4Yp+gmQFrM&&cMEz_nf8+Wo7)w$t^pE|WP zC0gI&OS6qI1mesT(zcs>^nQ(2;$*MnVn9$|`;uX0=B7=bIoDH}%Ctz-II$W^#n8E= zu|skB1Ge#~Hu%ut!u;(=^(0PmGVrP3mpE)4(>mGGpflxU`p;u6)bRCLMnGDN`?@JF zzcNFo=c5HqZd8PySQ0OMdpJKMOW&Y;QFts|jb~D3Z``TJKxPA%E~Q;oN$O^mD5iDh zp1!mU`O``7k>4)TI-tzLV*+u;c-65VOGdvfGvW=u%C7W%6^k>OU^BJk@%G>Fp$Iq- zx0H@&y4GcvOUv4+HmFJnK_STO<&(<~1LN!{w4-pDq4$ZlgO2nEEh*=Eb+uOEqCUU6V%GJ;*5cX@u8qJ|(l*XZla)Sw!tUAv zg$vC{nLc9|SOCm>W?H2HlmsRc7C>Xm-#CT>$TpXNx0J9nhes?!g@jw$!!p215S5hy zsBmCl6S*8>Aymy3BLiRnkyQ0*5YP}2c;>#YSu%mP`Date247Z#?oA#($9*9lU&RWZ zDkES32Hu3Kf>{#B)o)`TB^}|XGna=8ApSW~pU$bKV zjlg@j=XIdt)A?WCrbt{8dQvrV!+_%o&~xWmr{hpRZ4I{L8lQcsld?LY{drqp7SF#a zLhtGfAyW?NP3<)jdfq;GZ*kDV@E8jPwz-L-n4B(-`%&M$u|#;~hzC(xe4(+fjh6B} zUcL~>@Z8X7y6X8$PC|jQzQ|_z&ndpqANn+pCh>Xmw@#;s|2%29XwXBcNHG9mugVl`IE?}!6EqqIL?rU?Qex1F!T{Jb&*4a! z`0&A~2?%Z0>QwZOUG>wzKNCj7qC`|srw}+?K<92E<|f+tGPXs0NMvaTQ(|HoUuqAz zhm|sQT@};eUsf2Xmi)mi>f`8pt~nv>nJ#C#&ZMTIB2?yiPeRKgjzx65A(p^u05>NKO`STch=<=JY-sa1+s`SC~CH;$3 zs!TwYHmQUn4vaL4gimM#7~;=WJb+8FOun`JD1?!_BMEQ+>KUOU(6U7RFF;jNGWANJ zF|(FQ$}99TXZuILcu=0w+iQor5&Sg5uqqA|7R)rTM5H2u$+Vz@It}$7l{r>16@0`~ z5#+)_G2b0?_x$84i>dK;PeTG8#nil`7=h*bmsxFTXcNy42I#ELA5c=lE=TExf>2XXrF115BsSF>* zL~KHZ-J~W=P9vWdY==pE)Ib6-D_=~W)D{~~L^FQ0&eq{#-}$a&`!xK%VguHmnoa*W z`BQljN$slXfH&_;6Js|Z-{_mYJwTIbUTp%O`DOSExvFnwwu_?Q#I8-ahIh3jPP&41 zOou3fs3mDs7E5>AY0KSFb@bV_>4_a-{GxP{gQG*M;CVaTpbGYn3@tV87?lxEnnNqK z8#$LNo=+bbp$6scia{bR!b8j=BI??D%q+h~x%M>M$FLA1rR^diQGh~wEb=TZNDdlu zJRJQGcFC|I$w>21Nq(Zu6i_bLI0g_~r$cn*hs7lj&#cw>X(f(;1LOgL0d@9TOcv2UTLp;-HWZNiiJ$s4LaF727PGdl~ zvy!Sb8O^bE6L!R&1z&_vhG}i%2cc~=aIB0TJtk(XE!s+tC5LQI3hRRE3?J(R2|38u zw@M~SrFwX{q^*u7zls3SxbxB5bn|0dzoTc291#{(S=Jo-eJo1lM1Nb~SvtQys@__B zo*QY4h+amS*zL!Poa*UWc>w?w5LsbDJ9Ai3S_W^B3Q!0fE#avZ0#77q2tg!mV0m?| zZ-(*8nqHMb+3luprG>qI@4XbO)e++(^ua76pJ0Q-;nekEd2&f&D~D#&(F%*SSRFC} zmxRQ=Q?(j$RF1zI4ZRop8XZNr;I{ES=Zfop1~1yWv}TKYA)ExyP&VH^PG3xK@@>yu zpBr5$GLz|zX?3hTXmE-UtP6WI3htp*8OHSaY<&D+ z?r>V)z5T@{Af2MWB|fjY?RCY4H?T8(w*1#jTle#7vZdrC3ab-=b!MIUGmr>{*hrh? zggZO37C~BD0AD*i>Yfys0vz=slar(o9iUP%AmB{(3F4%GD@AOF5oLq9j}UI7^p`eD z<2$doL$=_~<6Gzqr{WA~x)3ukJqe|#BtkG&kN^m=(5mPv8@IUnomRDOFga#DPU$kX zf523S&@p8}@0UOKamn_&(8zkK6?L5)a+IGMtj7OIi#2q(Rs4^i0odH$Yu-AG_~CwW zYseOT=d;x0rm9liB`Qv-dqicB;kij(O&9K#0}U5!)fQ|rN+AKIRO;(kmDX+SL)&qW z2-}hv?{Zld>axP0bSI*JrF&WqncGUrd`x44Il@dcp3MYrg#Ai9Oi&>8bO`aZt*bf# ziYf;43_ef=LwrU~WEzP9%>Wn5i54AGlxV_53kPR0m?Qxo2d9ZCEnh07gh6fJ)Y4Y0 zLbKJ$A;W|ZN?a@Znp6U(dF2yj6w#obZ=pubaP}hfK^1S&s8#L(S%(}&D$&WwBfU~(?!6|FZ)|4J?vU8A2!0n*PMSYXfm^5&X5GPTBW@w` zNw&*Q6vWrI0W{!T!qd{e&w4jR)yHR_qrd9894pS0(m6nmLFoQEwOYlBo{~00YU1 zL?O436S7P=h(oA7M`-N)v)qjZi26E*4EAd^!fY)+jm}gQgN} zAUcJKxyQQ%fKe`^TJhzxb$xG90!i42<)pU&_o%NufkYK}FZJtq-z7!vI*)_^31m zVv(>cSbP>q*hIUHSVf8NGC{L9OvWDt7Xc^{ytn^^9GZ$d4hh|V{G5TQ_Zdm>SHVc+ z(k0qr4FD9|JxeLfMy3uR85y~p4whPnEX@VzRvQ_{>{&eCli?OY63s*s3XTs$$Y^@i zdVn`I*T5Ix8sMS|bpi=((AQ)3U+nn`$GZ$iZXCcvc&xS3!_XMKgpTO&vZTT8D9ZZ1 z9~-+A+v9)RGDkO?ZaGSE8pL#Z-5g=Ki*0Q`x%`F@zBbUoacb)(ew~6HHw9?$h)I^d zmze?dC(Y7lEX(}`sT0bFG}A(Bd;rQvWl{iqpaLF%d%X0Urzd=2L!ql^eCh|h#6N$@ zihjk?pl+;do<>4LyRJg9MY*IP{hFkyOY6F>(+_O5HNrJ%$cSdL2mq~@O!8!**%WClu z%Mv>L(?tNgjxriTi)8&OGu52VrOeP9sImC<-U+2Ets@mq2INjGdY3t?)>jw)x4bma zmv?AIv`@hii1yg4|h>6vJ z;U>k)V0u6u3|}X}^id#tn6aD`r4oH3HH>p}Q#IZZ-o$a|!bu^9L<{OfB8rtI}bbH zkCq7>{;ea-O*_z1cBPqJ*L5kP>{V54h_X<%;^(AfH*-^qxcv0B$pBd)c1&3%cxpZL zSEw7_W?8rWj$xf`jV2=t01T5wMu2!r>PWARa)C;;_Z^N87G7f)fS?vuQJh!`qXmLj zLAl(p_ehsF>~05?<}SokczELq<$|?6z-oC9(drbUOzwiAmaw5^9znQN{ivGKxRUJS z;pQ}w(V-3y50&p4+bN7f&TB8=LT_yNaLMPG$P;vTg^DkPMlQ{tExsFJ4zu~u%tBk= zjAHZfSB0}?bUx9{OrkicALO2FC^@}TclU$Dv9g79!JkqTdZ5j#fsLqP=OM>)Rb_|Pje(ndi&JnuL|87+(_IE50F^0-3I^UK^uZ#bAU}m zUO+IdlRgKrPI#WSjF^KZt@1cEG|}d9Osj}fD;&*oxRIA7wJ&xprZ&kP8-$L;WP|`O z&%5NnA?1J;oxpSS)o=(9JEYC`|HwMarncI)Z70D31d6-66WrZBxJz(Cp}3XO;O_1k z916vY6n7|Iyg0>LpbC_$54~sJPw&k70qesy$2!+}>~hqKRH`5ZoP8OOj)5?VN1~6H z8#scIR1DppC)XF9NCvrTsYJ-H&5tni;~_ZV=AcL*z=Q!tIEV+tQZzzLn`8Xu*%K#; zEmV(Ld=;*Q9u{-baDKGA8l?s`I50`?*|eN zqDM--K7on57@YDpib;|Tr_2^UZVR^)Hl3J|c@2)nx~FLA<$Dn93^`pR2IV~PLI#$8 zUpS=5r?En;_aZHOr)X^{0K= z(*scUSK$t~&_nd&&ss2>j7{wr4*6wBKi}u7dB|EOnwkvINb3LWrjO3jvbO49c0US- z3C1r8vU3?qF~=~NZeXhmG%q-m&)~j$yvPV?c-BC zAeM@!T$Jy6U`RiClAy!WKnxvH7hz9Izh*$S|B%r3LAawP1ZYZlup^9kkIHU1V7Z?W z;Y~~XEPKr`=G^D!m(;PX!M0Aq8w95*#z~!iPQpUdorjiQ$NO8Z`0J{|n_HUBuw7 zf{e+SaJH)ftJ!POM7z%9U%wbRKGx`8wkc~XE1diYKEKRpDi6Ou*xr4qM;e_rA(OIZ zcFeeDr5zO|*`mP%zN-6%O^GLHkDb4(Pab8V3o5_uSt{TXFlZc+>}l-~8XHwLi7GLZ z4DGuEb3MBrdj(hbnOQd=G8Q&`{#cW$8TY~u%kgx-LvCf@s#GrgV_kJhLv_uOTD6Df zZ;zxNs~GCbfPegW19poaG?Qd9XTl^uFUD2}g`@{=2Hf~v2LJp`do3D~?0LeeQ40WI zgu`!p@j!qu!W-nd*bSDVlDph=j87r&JiM_dPNXmX^f}je`~A7ur~=*v+TGrWrk^Q~ zQ|DF3!f3T8$%Mt$lz+}U?avFGM2SAE*5@p~(}GpIck>HKm>BzJkvD0sU)xPiXw5n5 zuS$d^V<3BYO;p(__xqh*GM_R%mfy;m>((?25c6czS=p*4#hmJBk6DA$DqeGGaz?y5 z))bB5iZM@h!-*P;)KM`oAV+YjHx!)bcke@({~Sc;d(>yJnB-V_+q2lNyiepBV_|4O zswGFXta1|7j~=X+0RTyu)NSaU0EYa1P(Hxb?o!`uLLM7wclS4;OZaAINB)vi&opmES*Yt_gm9a_6v@f4{=0VS0pQZWSLiT4u2n4t{zOXz5MUTI62 zK29(SB#6+c(}yaX<3U;Npq4O)C8`_%hg8N5vBIc*r+t9L3lPITJZwEwLv4>5z=WJC zg5+AOY^BHZ@*h7%fUUVz>84}ekrGL^{XSH8u=MQDxC}EBoV_NCq-q7#f%H2Ga~54t zjP?4%w=lmfgKZU2SZMr&f&)Gt0kU2=N|7s{h*Rw{H1kfc>92^G4Z?b?^GeE1gq!l$ z$uQNrV}NNcUQ!d$Tz!sdW|fc8zF{H`tc@@7aZs|uaHH;+ z*j^ywM&c?!@T&mpyrX%T;%DV{(HK*%Nk3b%pO|m^>$o+`^3i9}w?`Rh>tkD3v25CJ zG(L}vc9iCHCNv?GKR7_@9%dxfv)E!pOwC5kJnoEAM(h7j`}EZcv+*R7kh&?dvh=Ch zAJuFZN~u>rwAB=?iRX90yK6*ff713^NZlSD8@(0mA!tD83dJf%2j%Zne7FlF*))zP zbSGtIs!K3u6K-!Rxb2`V?%_Awy18+Nis0;gdpLdl_}{l0k&fqkl;37734n#~FhfBg z3cz`{2q)5Ti6DsS7YrKb;SB;~M$0z@>;y$$e9xemla^r25yM1sLAu6 zG!eRvlc7~Ig6LL_apJzfW{+Pq@7-vOeme$~qz=Qv`d|I9zE%&0)R#m z36)WS>Y$b2!~fQl#l@N9fRgr+XDHaHU)_6{lvl_R$HL>E%U1%X$b zyJs0v6Ho`k{^$6e&Fod`TA0fz49!ceWQ4 zd@Z?(pXRbiI{ol)av}S(CUoAzW%Dc|` zu+wDH2O#VBC~AC)W&TV50pC6z1)X6ZF1Ev zCr+Q8{{)Q4ucAK%?Z}6lk)&YK=eWq1#|R;38V9dw2G^8Fhm_IDfk-8A#Am8}y)OU1 zo`+T*oaJ%AmB`)Rw!pWSYk@a45zIucJ`(D;ixM{f8RXnfR|f!ineww+YOR;Xg&QJrVq(EL z6PZ}(;jN>iP8Ey^xg9CFIggRnb$Z8ReMoqqI_gS1x?H-AYbdt%sZB_;N28R(0{Mp> zySSS^Ho+ecB5+d76pJl2GlGGcZo+Bc(bmDv?95%=WA@-cOcKv(uql7qaJJVXCe-oN zArZzl?@RjAqu11OY`~3$-MB{fr10k@@08fn`507<6K`inQV+suDANZFGO#hhC2oD{ zc;H_6WB^;^(u4Um+QVyVD>jX?X{-MIk}P)YfF9c1pC zy@VmRS#q9#I~*qxSCMWRy~3xSTqL8>Y|?9TW;A0E*?7~8A5Q0Y{>D7}4LJ^w3uMxSq?I= z&S}T(s5TAo=_{P^=P-4e1VKx$U;dsV$Qt47{mxI@kRZ`N+?MEF8f`}eT({G*Iqy|F(X<(O8OPkb_T|ES0@Lp7PZ`0 zIm;%uSGJZbtSiR886iT#VlcNW3+s^CW&SzZp^{`nnr3bG3mkK>uu2Fh=`F+5s`tXA z6n0DW2`>8Px=ayT9P!3lR!ts#4?910H{RF=wSstU7#m!jd zRO9T|l+iF%pDED$XO}RPy=cem-F1vpTBkfRi2lO_%aAH%ktL`^9A#C6D8Inw;WHkF zX?CKU`kjM(Q1hi&bDuNFlRMgSW^-m?1H0xQ=d+vmvcp^R^27}l_Ws}!%*CF+J0X0x zdoKvPv7hwF^f$^bPgT4B_FCT$uGm>o{hfn{;ZUKjDzF8uCZ|vbwXT+^oNIFX7Y3ZVVf%acOE;G+Q_=MElQrKaWSHdFq z5GD4Zk#v!K33@p}#-gcelx7L3^ODe5P zjo!5G#LaS2PI^eh=V+|$n=PZTONvSlSO1S^@W7DSTtE^HxBYp<&=BjGASn3D_j@5P z6qtbEh}0}^dYzz|XSsqosEX>ZKq2<*2iMLHzGs~rNr=wqDhax~`|yYHj@&{O_6*Dn zvOvZISUoF;`Sp;Lg*o5^UV%~~@vt!9UgbRG^ON;AtfC^vtAl9w^r}8B-{>@ZBV862 zI*pfFpUEW5GMO(-q?=|Z#--&mEwNQ}h)KWZ38oOiHt*LSY&mCUMoXMIZv9E$IApwx zn>%;CE1!}Y>}CaN6Ue^tvNtMBO~X{Q@*-mzr-@_{CPe#T&?EK~BQJ1@vS1&mKSf#==` z)0qawbhGr@6aQZ#BdeX@gb8te>Dr%@{Hyg2>2*!XbJ4ioQNxjr-}pb!%GV%^C;;h5 zbgJc)WsaCI;mxdwYX&g{mQWf^z!D zPPH)tzQtZe(cTjjFBNX)RYmIPYclQyI*qD8`)7$>`(735gx=xf6#KWIb; zg+3lth^Muj!M$MC0Ju95vd;<>xs<{Q$z=-wp!wcFFyzSRJAGwMhCcV==0)5`k znLfHF?`X|m#KAQTi(7eMYz-o_j6tLZDNUlO_ygDItyrwuS8x*A+gH|(sj-9#;f>1q zZYX(@M_jwOHwBI>r5Y_$)+HQOQ)c8Y5d!FN(Q@wT1Kr2TK*n*F$|Wb#@uhCd#Co#> zL*X3MqlJHTtp#=!2AclnEN^2%?OYQz|46@TRFUqdw$@=yOd^JD*+X&GjS?7#nB-p)g=A~7ml~gkm;Gm4>ugkOC+ja|_|yTKl7i4LaPjsw%pIc9 z9Tb~Y3U_*4`i9_?*69N0gg@Slz*v)n)!;__EdCs{g@YHM7qetIsXzfb_K`Ja<8 z1;OUtu?`Q}$F!mjoRWh3Dp9unz_yTKHIedKxDT!7eaqY45Vy*gqoUYPVd?H}Kd4vk zZ+@p(#^u2TR1%+cF*e4pXAF5d2QRqsj`h;Tf#)Y)@$ zAGP0(b&53okxdr0fBb6kb2dF(o(Yo~sH8RJ5NT6WOcFgDL(P~USF$j40Q8I+Tt>mFl74&8s|nAH=$H^%3n?Xh9EC z_uG#R@}(yF47fyKplkv<#WStkA3cl`!iR!br`l<}rHKP$SM>{5FqRajwj|S}I8g~> zyopEYym_(G3ya5)7shSR+e`QuxhXqX@V;IUoZ+nAd?QQWwmp|bNsq4wG-u4gHj4`L zwk*@V^#Ad55neIP%!BWCJ}V`Mf=|?x@qRcqS+CY)86ygbT4#;H+nvbW6Xa@k47^j*Ox zMlf6Vo0!`HLwXLK6P~}=%8!ue$h4x)&#HKcQxjn zGFEo>k@>O*{;D=F-Fzka=t?k%1+Z=-8TUex8UY3=lT};4l;1_mF9>5mX&_L@sDKs8 zOUEY1!EY2ZgGFVOyzv1Em9^I&&@7fmrLolA=vPmKan)_O=HkCK=;&sh`O6u^s0!mQ ztW?#P5EfV_Z=lg>%<%3woa(WAKBx0P30hZdkuXXJ7s7hC@~N%YN(!&;l$R1Tx%E~e zhG?yi^eygsEZ4qe%9%0s5^fN>dNUJ0+xdRYKzH%xzXL6Xe$RVtIpMeN4n?ankMAvJ zKW60G`7qV_{p05glIfh4rFlO+3N-9^yhdFr*v!g$4e`7 z+{{_?tDCaVjiCbiwUTp>xZ3R!wOHbGOqWsbt;?Sr9$+ScOP~E)8AB}Dkc zl{g0|JqSaGKn9~mU~Sj{qq1kS0D0-bGC&eAo?xPuNE-uyLL(rv`y>SCJ6U{LuWV<}{;SEKN|)aEDzA){0y;j18GFepwa+!kD(O60^tx$l~S zJOUJApwDlM)EA*?t4Ed{8=0cV*;`_u{V_{#i(gVa_}WP8b@(NHI4)o_)?{sqTV((jl{J_2%__Yr~J2f+5u*vOYlv1=# zJK<|3uw=YiRPaf%YFklirX>ehwKRbD%_s(|%W!`*_jB4R;7 zGi+u1Po=E!KYo6LxJ%fHp1Iv9Id?g+^)dkUJI6IUHWmfNgbZ+X{1*)J5#qDcC<82* z%Cf9uqNMKgHe+755(~^)!5^GCRrp->|2e0Q{|R{BBrE@)tny^u->;4yhyhK(7NL<1 z@-ES8Zo_c4ae*90Uy+;{0Uw-7o0b#-G}1OHum1$#^oIDzC;=_(V>bInq7!sGS36l6 zXQr|>yBMG|V2{df5UlR%V5E5xQ5jNQ>nRT4(6+4SaqN~HcUI|h$invuDg&|nP68Wp zhKa_2?C2cW=*r5Xpv)L;m@QaP;-{8TC>lOc@h%)dF=z#(@F6BWx2M2B-u6N@(0u?v zK6p3)6Ah3pZ#ddHV_=2Cv01RSg8^7uUy>uNM=6c)?MMKaAO=3W6d)V0%+u~Ip))~Q zg#7^738n(aK4(3xQ3%0?5s`nJTtfe`m$_vVYnPUqpj``S5UB|D#IW0Aghrq-aFB$D zDZA?6rC#`JWKFYXBnQ78T8nK zkusCpO@M)p1Pm_VU?Q`gQc&mhf*pm5j0Z5)f= zc7STl0W)U6weXW>p99-^9W!{yoAOlfNy; zlq3GJYU=@Kh|Zz@%*=)(Q_9b*itb#qxjT*fuQP2`=Q4$~GyReDd9R?wORp70HH!eW zRo(r3OJ9l~YPpXDKm~zyL)g1Ba1n`(Fa2DkccOutD9X zwLCk?EA1ElTnbPT=rX0xUNUT+dRQDP6`13%SDcsy-G2O&FAr6sRDuCq-PjuTf8<17 zy!ywFFwkM_7Z2?=5`9OG6+S0(_a~t_pp^XltEp!n>tZZr?{rUvM{vH##hasN`b`4V z0u#Y?iz;@3i;p=067*^2YZmWgY#aUi-ruFrehCh|x}YDC?7IHs^+(_j-_B0mnWG_h z_gCAT^6u3hD=hnvu>#j3w@)QWBQ1H>ZhrRsc!nw{@7l_}|3(YPJiJN+?;*&V1Bf6n zOMl_;O}}30G>4P$TJ?UB`zY_}-BxeW>U&g^UMlp#_+sJLWmw}8I|gp6mHrzd0(8id zvG&3qcBFz!B@e}S$grAe&4_wY7XYyooJ}x(&_rd&M^5Uxg27_nnc=CjlZ zI%~s6OtrU3L6HKEgQiQzzZAsNewk#$sUm;iA{k82`8CL2x|VeJZg2jD%oHu8)Tm%Y2h~a3-5uzAKfddL>P~cC0kw~{_q*M#S8WYnUQ%|9J?x(Ne12il&|=N!qeZh| z4LS7+`I-H59a-u<1hZ(Nvy9iY42!n#G!aZdja7RjRkix4hh(U3+!!T79a{dI&b+8u zZ2E0{r;0~`Y<$w}g0Q&A-nS8%S2b^T8cKqkQpH~1O`hYiuB?azANyn27eu{vT2h=E z&6j^Gq@Q%kIe*LR=>*3nWR4tFYA7gJdHL1E$|#);Hxs9kicxk!VZ_%7mr#wLwh*sI z9TirSz1}#f<>Y;v$V%F%^FUhAhNk{>&1*M8m#T^s6c|>=wuw+Chgw1)me5+r=)U0~ zYOa<6x=v^v7k9z}C~rZEyR$MVT>0d=)_Z*P0zZ#zMRO?Vb?23L92ny&nFtnif4E?K zKQG<4vho!Q7sv$(T6_$9e$4{sOpFMfgm%4X;WI(T6q2%x|0J+TV2Ik-+QKh z6{?$bf0vcVFihTY!5*^(kCyhBw$1*f{f{2i_}arK)#BBKMPqMJ{YAT?k(WQ{vhdaI z8x!lU#k)^;?Ja$+4vS;(84r#Q-u%d^OB9oI=nA@{s`yc9B7CaTUhFNT$y|TUUH!`nCY-v^jU?C9BSopb(LzFfVx1v9{bj=7TjvM2&~_bBwrB_94+;dB7+^@u5JZ#?q5w`J zBGdkcfjr&$dxt9Fvq)&v);f{~?CkOM`ka$h1I`0H?#$XI7FvWS*T)w7)Y{v92D9mp`Rk&c6Q zgEPPF4i#WD4XCsPJJOT?T)R7A>~M*>u8 zP~5cO{aweKCnkc2|9<)TU&KX+VEyIrys0~)#%fCeyYZNflNi7^G60`kzC|r z#9`KVf5YoZ)zS&@X!71j1eymprLO)l3rqZ1N^>G&BI+LY{_B74>hU_3vN=a8&kVwM zk6n!R3B7}>{_$gqRtW#bqT3RxmDGPYv`yyLtAyzfUVqS$;HK^f(6wowEm0)&W$$J3daXXBZrAS4)B#UI0R)VdzaSrkU_}l1=MxR{u z`0p)?3@MCJa!v}*JWIt^O$VX&x{>kJiISd8^C05 zvkML>uXdYkj{k!OlQ6-E=p<;)`C|UmO@2`Im|=G;WHL`Qr&uK6+QDwinpP0Zrd)WM ztkawf!C2xw8md8CFFyJUt>On7(dZv>8tqsmagGKhyf`^Lt}{F|Mn?76x!RpN|}c(NI=0rNBl3RZ?qso>|6letdirL5sl-WdbaI!$Qum)@so^3ZG0i=(YHKGJaDShC+Jl?f66WQH!Fm`e|J*uz_jPb=O)#H zZ^B`n>kb+DowJA5p7l77Iy(F=E~aF@hn%J12*Xe1pFH1_LL;d)Z&;7!+;Gpq>KYT6 zZ%jC@_jAqTd((s-*PAvuoc#s5uEa+*lJ}G?@mBY9Hqqdx7=93lq#Is#Esf+uhq+M)0J+BfAPnfy6pVQk z{a6Gga2;-eesi{n4iP?rh$0Gcd7ge@h_*pV{-NjE8J7^%nY%GT&P2@NW5<1|#pH;h z8AV!NuLu9)+eALSGIIHzLNXEf{A5Qendt3=)rL2wo5gcGc|s@>5Z+nNamD@j)61@? z`}g~_K^^g8tQTqnqs&#*k?^14ml{v06cRZ?4=I_cCv5d&%AhGqzbBt#v&4a6LeuMY zUjnyT6%GAEmxZ_c0t{QFe@CChh$l%w-=NDtR&Lp_Y|etREOjQpBrt*(7h8hE(QZLW z)!o>nn3cl|5=4L*Hk`uUXK^4n7V=k|d9>h!J2u}4Vd~DYO25!A{pIuPAf=oR!8-ow zrdVuEHYQd;_nDe;wJ}tZph@I;YnuVHIYp`^ze$S;H_=zOr0Nde_0+N4#97WIjTc7` zzyE%?c>L2f{N=DYOnl%UKW~wg={pKd{iL`^*;C4{%6J@Cucu1M+ILt0nb9P=cN+3# zSyX&XOC;r&ljPQ zBo>I52x?q9G9-=T>gRXcPjVWYi8l;zhd58SQ2-@B{;q@`H?`uv>ZSWMw{U(TZ9-*c zr;4i%*Pt-#L@*>ocZ^8-Hqgvd;C&;WM?2SfveiIHAy%E*) zEkDl-6!P@A*b2Jexq0rn`TOp_PqrdI|B4I<9q2h1Yv3jCkB;(F(^jQW=9Mvn56#yV%K0P%^_Sm3h4l%j*pVC@P#PCtZ*s0Ej=8xiO%})kL zjVsCa)BsNR7eeniid?nz9rF|y&9qira9XK^i_s)QYsu{7NLsPle}nc^FZ=?$e4%TfM`RXy`!NNSNsVT*E zg-dH(w{a-su)*d2-0j;%sG(HOgXe24xp>VP(>CXqbMb+qhrHuytkWevW%XcyI>Yl= z1z2!&@2Ln$n-BmSU#vg?3S*#<6c?>$fGE4|A;B0p_Jaa3!)#zMMR)`S1(bndh}G9E zCv6AlEE^PMUg!lzr=TR_>n1`?&lP`Mkq3IvyJNhI-1*Yw+Fo%q;7 zt4>2Qwv=qLD#dBLxS|Uu=@SqA|2muZTV0Ee)U#3J)y;26j-?G54uTZKT8fgY{Eqvo zI&zxb?=_^?RnQ3)gPO^r-}fjP8K=8G33Pzk98T9T@u1vs$*|P-1qP6O#(9chozXV} z`t2lMs>U!3e-2a zNR8ZZh*QN?g!OU}`rEyAM&ydI|M>X@V8MG}YL=0n1&4?x}m^C{ZrBDR-dhYuD-%DI8|l*)S3> zsWQbXR#oYb3#;?;TRU{ixT~@3Vvd|m6KrRcZA$`%&CT|moBg9^)9Za}5_>=u`tKZ8 zQQzs5ZL1JVR`1d7>Wz7v*2q??2QE?J(k^(a`->SWbM93~9wC_xZ_PDcmKTlwSVFxL zxH+iF8CA+qp%J#_$MYN=yRaDH?JVX>kQ$97oa# zw6sK8fj6^mOb^L~&tkkuD1NH+Vvs@5*AMYWDy9mzbOeg0HVksq*{k=nJ&HV7pZ|Au zrep5U<YWm4 zVryKqjA>Q6rjW*Fj{$WZt7hn^oN!OC0X0rMTF01IAk!=wCyRd%hyIFH0bOdzixspa$i|0X9(}VPuK1ZsI6cb`2WQYe#hGHk zPe72w1&75G9i+v0R;H3BX}!Kf!Q(qHw&A?nrY?nLZ1pNS&-aHgCp*!kUNT(Dg(h5i z(X4@ln@nB1iTJSnz>kyKEfm?eh^GpwbKEu(o@VwntE8KW8lkInQM2 zXi%R4Qh{r6^9?8JWy+FuUUjr{KHL_%d~s~3hWN;{u18RAH9hn2i<|2xNNYefgWc#? zm$jVnO9h-C3vc@GwpJYsxgTg=9{#p3Ac7K*3$#Du)rbHLzl_4zmlyEKyPZ%lm-oel&hbNIXM>^Lz6_ieYx^O^ zs&_cc*ke9zuS!oyE@xOh{H2_TbyZC{L9uS4v=L?+c=)8SnN zGf$1V@Wl6{0vRV~yqDY9gEp;f$xnzaBBYB#!;b`mj%-Ec#IwiiOy6+uiI2W$Nz1cT zc-Kn0nxM%1YnXA3y>+PehH_(S%IAwd+pKA+_1_4qqPc67jKF9bI>wLkX+AB*_~je}D+yo8 zswlsp|JpWe>d zdpMgsj&e^pd2wHk(mfeJP$Oi(pI)<$%|xx&<%BeIM54Or_MbzF-D8;Vg7S_`)me(( zI9bqW^81QJo9?!Cwkl3o&6vGT67(QE_!ZJmB!;6l>0bO_g&E%=wW<4}Vl&3{i=3W6 z8)2=g`*oa7i^g|ity3EZzY8i2-_Cj7{+mC40nX!3{qJX~qx7Q(#ybMyiC6eJ^PNHO z#CEJc8h^}>6=42==ydt(?auc;WM2Ahj>k0LE6Kc#Zxz51m-CMdG)x{AS0+6^Bcrdh zzG*rr$;$k|!%_(!4kiHEUxR<4p=HX%7*eA#gTjXa(j)2U;}N`El{y$vWTMPQ_WlJ5 z&UjpgpqgYX9)M>YoF_+>!JH&acYp9|o+6ylb0_E`;@YM-qf6a6{VYe5+^SmoYU#cI zS!%dY1@`2T4Q&b@txcA{T>)jv7|gLK&YH$Bhdgh)#oj+w#dls$&-S+`4BM8)V$hdxVZ=X46446ow- zYTb3?YZV^xGB2A3a_P{a^uzQGMmMM4;HSCjpQ`3@6v-<>Xo9^+)YtpCiSXLc2+7Q~ke*JzJ)E1{>^a!dd>zpO__@ z{Ga-;s6kkUDU-~I!H1H5JU)G?mAF?!cuQ-A)2ire}ta|98W zuv^@VycJ}r&;fHllm5Fmmm@}_u zfF%VYb=IJA^Gp-oet4Y~koMB%HN{|O*BW8t^FT12T*&aXq8C5O><@2>23e?+ss;?603 zBcnxiGV^sf*{^u*dp$XO-NiK*8#XKR_vvH`)_T{QrF6$|v`#k&y`{<2g}t2m3!@b` zI|H~G7$03J%CD6W;7m}!X1t>Gk8ACbB?0Pbnf5PdtR^JLq`KwTvk{X_Ibx z3D)G|@U#uQlNy{3K!#cgevXHL0}-b?o;m%Fjh-<;kJs zE?O^l(JVmJ;aT`B$4_hZb#K5!p=I*d_W?7lMRYtr#%i59vobAv{HmHzAc;g5Tg#9u zWRF3*7AbQqZk;Z^z0FH%6(QoMi$*Scay%nS3dVo@Tt^#^e4|L}sr1u|u^s4A7pcPSAfOFZH|fUhmz|a-t(9chV0C|SCyw6;x@t+{oEzXHPb#3f(C)AzUnqsz1;(7>5@z6&Z@tMfLV@#4^bY)!9b|jq6(dx?bIM_6;v2FsZAPV;wmXKyEkK*H!pd}U?Cem2P ziePJwG+rwTx4iR!s!L}HY&MmsLw(l@x!gQ1D=N~IWKU0IWb~m@dyLvxrV}D67Clq7 z*+PPdsEMPz#mCRUMuTpiTDN`I9`zjE&)#n32%LA_ z2>sqem^jHgnp*oFbr7+7ZphOx`8np+@FaaRkzO<6cJd>)S$LlN#N@lbG+~~~h zQ<=P^@yoIe9%39i*j=4B2BOPMh=27-MlTNlesV8Q1Bg?Q;o!0<0tfdPj39!-Fr0`o zyS!%-MniHs3feab$mW$ZOgibkDF;*nNdQ44E;>5e&E>naPMOpgHWYDF^3QETGH&ja z3I%XAGd+QBs(QHlP(delID$f(g@?0*nwO{aZPr^7Zem;&$`H@H?`QW7-;d>PbwyY z!FWxG9q36e1`UdpT0+n18H_#BMc@H?7Ej3ku`>I*gSWs$f!OBD){v=A%nQ{yPlMtqb1q}xD(T1j-I(!)`fjeAgW zo}F?&NUMl)THs#wU5JlQ|KdX`8sgy(o3cEwUorh3KUYBef9Id$Sfzo{1JdoPTcSkO zmSxIUL1Nk;@LQH|jy*X#bn6WpbGSCEqRl7FHD095pOz4rtgc(~s-UYs(>Kx+81d{z z&l>F}WTYgCMWeC1u4G_S1}Jw=MB`#x)vvDCFo=S{xhC#Ihq-bID=H7J^HeOx$!h!)2^Dj0KW zFZu0c6($r&oCk|=l2r=S!>i1)t8q>QQDA-)TKFp?X%$gE$7MvTL4#P@wQ`oaOe0o} zXZ)yCHkEbRnUvmqsW~^*ltG9ei|D6tZD4cy`Qz=I;n8O{*$;#az!*(1l7txnwGHXm#)lf&jy^o$zjZ#oGu$5Ef}5S4w$lj|ajpAeS?@@`d~Cka_Ll`fGyCb#=1b6Iv+D``mi0 zS0T~Vf$@-&pUPED)C9SKS_Y8d*oY~)pb2J!J4#GmyV}VB*F_9)122-~6cMSxqgG=dfG7 zNGg(_Ct`hW1Rip64^-so6-W45;cO#tgX9}`!Jo6&usNW7mbZ^c`ckW{{pMNz?&q3j zT~GO^S*;Q|?#nGj<%{2lnF+|#VIP9;feTlWCMS26qn*pSGbbm?YSTaLG z^{1JmVWfIdHalWdO5@-2bKkP;*+99tpA@6CM|M)j>%+kbBEID*7GeoOFckAFq;~m( z@h83-k9-aUI_pO6y?kj~VvGpiEK{|+wso&f0&|9Hy=kzeMz{kZ#!8jS??2HJP$4tu zV!ex?RVvOjtFkcxuL!nEaAePit3Lx8Gn-^VoNbLCA`nvcBmSn4!zn?2mE0srhX4sh zkTh361lm$G47#fmTj@*m77EiVYKxWo6m>yN}pPKY>4|D0fDl?dKC3r@Tm ze1)t$IqHLt*ovBez7-Qo9K8HOJb9cihS_uzabUYx>&pRB>~MUDl>dXt|M_72B0p$3 z0&K#m^hGF@+jy}(hF@hG1`?!b&mrMT zS(Uns4{}bACfkUNEZL?Qwgv!P-~o~@m8pu5etqitR@A8-H1%oko4`Rwx@EnvpkFFi zaN4h!*qF`Is6Zrn*23ej{PT`wXG0dq^Q0J9KC;8S*&naL3S4m&AN9h*Ub{L}!zTj3 zSv>_O8~0>75bgLF8OC%lVs2LhkwhuiW3>z8Sx^c&XpDF;=AmhV4{WRQw*B-mpQztj z_fmURIr``a>(}jF_-MoXrHCpgy*if{Qj{|y^NgXCFN6*Nt;*V{T+Z|JA{AN+sZLVs zzA<~$r3A8)RD*;p{;EH>=0_HO{t7SDvbH!{gQJBn$xZxe`&E<&yPvMXoYt#3nMq_neNtf6 z@-QX?X=i-{0W5urvRxlw=u;qY7!kcU6DEzmqV_fQ4I6bVc4`g4I$Yv&-uTv}oP0a37Jr&re@)}lo9~Hxib{IOd4W;adyfDskbyVBn#_&ne+yxGC!QbmJ8 zvrhcs25Y`YM95neXMw-%^wiRBeS?e6~HG4ax*2tt~U1Sq@z9&UDtBWS6 zfM<5}DoC&HVHl%t0gh)ZO&+t#xZaCO@2kZbtBy+1O|&$yyURBiP19}vA(Zi91Un~U zD$GY#r2i~UR*eIFD!^;?1p->y3{nylO65;69x;pJMimoqZXN5`foZj0q1jwUXU^j= zk0M*lfkT(RNTAy-UgXj^R}5F0e+_YT^FXDL6t?lQ(^Mx%q$+UVmova{-x<+T-P%i) zFMn3kd1|U94uaV3yjQQ1my|7HJsZ9w>zA`+LTPw5%90+rp%ZMe-GCz>(=xhS97E~2 zWe_~TzJZcBhCgXfp5^~5|D3WMBtkjP<7XrdwPV~(i~mq7IfhJF^L6{u)=^1&i^Ysm$FjxOVL?H$8FutScc_$6fa9(c z_UaZ-5!ANx=VsYDlfbJDq=eFzH2*MwvK7OdUjN4N`0k zdVRy3Dqa@w{t2e`1Ji#oj zN2DqYb+@DRiRjnOCBlV-@x;F9NIx05=&e0+1OF0fL072%d{S#-WJQTQoNJODp6!g? ziMtb#_DgV@(-GH*xdoq~;%9ZB7DplEB=eRQ{qch)#NDliIUzc7*0@^-@CqYTT5Dfx zQ67Ny?Xr@&2bE8Ex4%P?Rnuc53}#DbPE>)9{BGSW4*DrUvTe^DF3F;P@^WA_TO>wA z8LR_J1Vb>=AHhS+GmJzMM~Ws+R9Qq9&RVAQky#`>bKp@s>rI!V#5%=(B+imUyofE^ zK*Ym0Tzum7IP&czcL0*p;WQP$)91aC1;g~r=sHi%Gt>&`()GUm5y}kNF9lxgh%nhX z^*h|?fGS`MYy{y$u@qU2tF#I}U@mD#O(j*nTdXBiw&i{kth=pH#i#yLrG$JpL^)3# z6)X-1e{m_b^vgR-j1(K){8#8rS1(uOi+Z-xRNXsTFsCL+{pkLwQH#H0TUf874s0`I z<4C5(N4=o)43+ygKXZrKlL6)DQPapc7}+Lo>Ob1FszkJq6vpI=)-oR%cm!LIdC#}N zcyPGD-o`Sgi5_S1dC#jexR%S3=C?f!3cikR)M0l&?j^0R>eA$eZ24AX@W{JfCS_RF znRV>dPv!^)YY0sYAZ&;ElVawCD#RsU{G1t$)IA5x_^bqbT`<1U7Izu#Yy(VVlFA4& zikj%B!I;d9ndW7N22!$w&y3Y?JHr8@Sdd<0b3NOu-fg8`WkH&<$NA8YClowPP3L^l zj=s#={wqtztT$Bo82hQ!*@u+Oz`b%{yxwT;R5jPo!pS6mK;G;Gm1cbLqU;Bw&3XFJ zkmV5KZe~OyS5flxlS5TMkHQOh4Rhuh4L49)5|DN7)>3|#3#?6<3vn|Qb()sDlaRt12hw=+a;&#{n2qr_`g<N_E-Mdz2KV$W#cg*qHd#Ju5{DF$qHJ)FZXQ}CGlB-Sq;-1A?B@htAblG zwe*zj5U6r~gfw@Em5Oyo68NUFq@$IVngKeKDj{8(SMtp}zW+h2q+u`lu%H}wR{QOzY;k!E!L3 zp`|D!i#%1&?e_u|SdID*;KUlB&pnyrky4=4=5+9nlhTT|gFRaM>pAH)E67I`F8 zkRtP?R}wJw!9U=>somEL%MDX^7fIUm#g^R3+SPWOD9=}|AomdO>NH4ZZ3}gJR(EM` z$?G|Nq{h<9J3<2*f_czqk5xApZ9x{4K8`1d(D5B25X*}^ zk6M7}DLODF+o1fj6 zBHC4ZSb*h=jj@h3fI0Lx>|LMSC!XF|AH$3vazvo$4MlxC*w|St20h;g*K|pCT$FYo zc@pgv9_lXP@OOK1VVu^Fpq_&+JD9Y0=LCRJ-M>KJr16QO2qQ{KY8* zFgJdo$g-ENtL|}XAAEG)pdygm04J-~eN$_fc#sMMSCw3&Vd(KzGrvb@!;XbxIVjthLTP0kTO$i0 zuip}BsN-g7M0cmL~ihwh2X6sJt;&Sk`KQ1{Ztc*4-yF zq(z(pdiq+h%ln^LSKT)Ti+{js6x$-d33%&@6x;_zyDS%Km;iI;nS7bpmHCBBrs~uV z-;~&0)9nq}Zz7cTigmT!`mSB+7VU}7*yQVU5@MwUODFMll4`weXogiSw(q>EJZMX# z`Hp0qT6BH*cVb`9KGn-6{7G)F7(Mk1S)wSNXuE8s8inr*5R~iTe^m+V`PVfEKo>`( z>iyB>YCRBRERL{oZ(}=P&ZW5iBt{i8Vqp7Pz(A!UnW3=?nRcHzQPB*fVrC<)z`_Sp zJ8AmvcUA>Of`4^4F1n)A&|2A{V6(R#JIDj#{JZ{2krI}BsRYToD2ZCCt2fmD;fa=M z{L@BlAk-6lJ!9y>!JCiz*U>ULFKyuKk|TuzUHuD`r@mofw-Y4D|HRKfY?lAu{13gr lU-84lGQh-Kdv0=(I=2g^Ffm>HZ(`!VIr~5KuKz1n{SSleBoF`q literal 0 HcmV?d00001 diff --git a/tauri-app/public/audio/presets/tiexin_nanyou.mp3 b/tauri-app/public/audio/presets/tiexin_nanyou.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..2f2fa22d51838ab55bfa16722172909fa4923696 GIT binary patch literal 49556 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 gEVA`Xl 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 E4;c8W2mk;8 literal 0 HcmV?d00001 diff --git a/tauri-app/public/audio/presets/you_pingjing.mp3 b/tauri-app/public/audio/presets/you_pingjing.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..91265b20c29278a0396e6932f5f66cffa38135fc GIT binary patch literal 52436 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 literal 0 HcmV?d00001 diff --git a/tauri-app/public/fonts/DouyinSansBold.ttf b/tauri-app/public/fonts/DouyinSansBold.ttf new file mode 120000 index 0000000..167b5ae --- /dev/null +++ b/tauri-app/public/fonts/DouyinSansBold.ttf @@ -0,0 +1 @@ +../../src-tauri/fonts/DouyinSansBold.ttf \ No newline at end of file diff --git a/tauri-app/public/tauri.svg b/tauri-app/public/tauri.svg new file mode 100644 index 0000000..31b62c9 --- /dev/null +++ b/tauri-app/public/tauri.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/tauri-app/public/vite.svg b/tauri-app/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/tauri-app/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tauri-app/src-tauri/.gitignore b/tauri-app/src-tauri/.gitignore new file mode 100644 index 0000000..b21bd68 --- /dev/null +++ b/tauri-app/src-tauri/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas diff --git a/tauri-app/src-tauri/Cargo.toml b/tauri-app/src-tauri/Cargo.toml new file mode 100644 index 0000000..4f0f1a5 --- /dev/null +++ b/tauri-app/src-tauri/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "tauri-app" +version = "0.1.0" +description = "A Tauri App" +authors = ["you"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +# The `_lib` suffix may seem redundant but it is necessary +# to make the lib name unique and wouldn't conflict with the bin name. +# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 +name = "tauri_app_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = ["protocol-asset"] } +tauri-plugin-opener = "2" +tauri-plugin-shell = "2" +tauri-plugin-fs = "2" +tauri-plugin-dialog = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +dirs = "5" +# HTTP 客户端: 精简功能 +# 使用 default-tls(默认)+ json,无需额外 features +reqwest = { version = "0.12", features = ["json"] } +uuid = { version = "1", features = ["v4"] } +# base64: 使用 0.22 最新版 +# 注意:Tauri 内部仍使用 0.21(通过 swift-rs),无法避免重复 +base64 = "0.22" +# rand 已被移除: 代码中未直接使用,uuid 内部自带随机数生成 +# chrono: 时间处理用于缓存索引 +chrono = { version = "0.4", features = ["serde"] } +# 结构化错误类型 +thiserror = "1" +# 文件锁(跨平台) +fs2 = "0.4" + diff --git a/tauri-app/src-tauri/DEPS_ANALYSIS.md b/tauri-app/src-tauri/DEPS_ANALYSIS.md new file mode 100644 index 0000000..c481409 --- /dev/null +++ b/tauri-app/src-tauri/DEPS_ANALYSIS.md @@ -0,0 +1,108 @@ +# Rust 依赖重复分析报告 + +## 📊 当前状态 + +- **重复条目数**: 54 +- **直接依赖**: 9 个 +- **传递依赖重复**: 45 个 + +## 🔴 需要关注的重复(版本冲突) + +| 依赖 | 版本 A | 版本 B | 影响 | 解决方案 | +|------|--------|--------|------|---------| +| **base64** | 0.21.7 | 0.22.1 | ⚠️ 中等 | Tauri 内部锁定 0.21,无法避免 | +| **bitflags** | 1.3.2 | 2.11.0 | ⚠️ 中等 | png/selectors 使用旧版,无法避免 | +| **thiserror** | 1.0.69 | 2.0.18 | ⚠️ 低 | 旧 crate 未升级,可等待更新 | +| **syn** | 1.0.109 | 2.0.117 | ⚠️ 低 | 过程宏依赖,编译时 only | +| **phf** | 0.8/0.10/0.11 | 多版本 | ⚠️ 低 | 深度传递依赖,无法避免 | +| **rand** | 0.7.3 | 0.8.5 | ⚠️ 低 | 旧 crate 依赖,无法避免 | +| **indexmap** | 1.9.3 | 2.13.0 | ⚠️ 低 | 旧 crate 依赖,无法避免 | + +## ✅ 同一版本多处引用(正常,非问题) + +以下依赖虽然显示"重复",但其实是**同一版本被多处引用**,不是真正的版本冲突: + +- `time v0.3.47` (2 处引用) +- `serde v1.0.228` (2 处引用) +- `serde_json v1.0.149` (2 处引用) +- `semver v1.0.27` (2 处引用) +- `smallvec v1.15.1` (2 处引用) +- `scopeguard v1.2.0` (2 处引用) +- `rand v0.8.5` (2 处引用) + +这些都是正常的依赖树结构,无需处理。 + +## 🔧 优化建议 + +### 1. 已完成的优化 + +- [x] 更新 Cargo.lock(`cargo update`) +- [x] 锁定 base64 版本(选择 0.22) + +### 2. 可考虑的优化 + +#### 方案 A: 使用 `[patch]` 强制统一(不推荐) + +```toml +[patch.crates-io] +# 强制所有依赖使用 base64 0.22 +base64 = "0.22" +``` + +⚠️ **风险**: 可能导致编译错误或运行时问题,因为 Tauri 内部代码是为 0.21 编写的。 + +#### 方案 B: 等待上游更新(推荐) + +- Tauri 未来版本可能会更新 swift-rs 到使用 base64 0.22 +- png crate 未来可能会升级到 bitflags 2.x + +#### 方案 C: 禁用未使用的功能(已实施) + +检查 `Cargo.toml`,确保启用的功能是最小化的: + +```toml +[dependencies] +# 只启用需要的功能 +reqwest = { version = "0.12", features = ["json"] } # ✅ 正确,只启用 json +``` + +## 📈 影响评估 + +| 指标 | 当前值 | 评估 | +|------|--------|------| +| 编译时间 | 正常 | 重复依赖对编译时间影响 < 5% | +| 二进制大小 | 正常 | base64 0.21 + 0.22 ≈ +50KB | +| 运行时性能 | 无影响 | 只是代码重复,运行时只使用一个版本 | +| 安全风险 | 低 | base64 和 bitflags 都是成熟库,两个版本都安全 | + +## 🎯 结论 + +**当前依赖状态**: ✅ **可接受** + +虽然存在 54 个"重复"条目,但: +1. 大部分是同一版本的多处引用(非真正重复) +2. 真正的版本冲突(如 base64 0.21/0.22)是传递依赖导致,无法避免 +3. 对编译时间和二进制大小影响很小 +4. 不影响运行时性能 + +**建议**: 保持现状,等待上游 crate 更新自然解决。 + +## 📝 维护命令 + +```bash +# 检查依赖重复 +cargo tree --duplicates + +# 更新依赖 +cargo update + +# 查看依赖树 +cargo tree + +# 检查是否有未使用的依赖 +cargo +nightly udeps # 需要安装 cargo-udeps +``` + +--- +*生成时间*: 2026-04-07 +*Cargo.toml 版本*: 0.1.0 diff --git a/tauri-app/src-tauri/DEPS_OPTIMIZATION.md b/tauri-app/src-tauri/DEPS_OPTIMIZATION.md new file mode 100644 index 0000000..570a5b5 --- /dev/null +++ b/tauri-app/src-tauri/DEPS_OPTIMIZATION.md @@ -0,0 +1,159 @@ +# Rust 依赖优化报告 + +## 📊 优化前后对比 + +### 直接依赖变更 + +| 依赖 | 优化前 | 优化后 | 变更 | +|------|--------|--------|------| +| rand | 0.8 | ❌ 移除 | 未使用 | +| base64 | 0.22 | 0.22 | 保留 | +| reqwest | 0.12 + default | 0.12 + json | 精简 | +| uuid | 1.x + v4 | 1.x + v4 | 保留 | + +**直接依赖从 9 个减少到 8 个** + +### 传递依赖影响 + +| 指标 | 优化前 | 预计优化后 | +|------|--------|-----------| +| 总 crate 数 | ~376 | ~360 (-16) | +| 编译单元 | 较多 | 减少 | +| 二进制大小 | 基准 | -50KB~100KB | + +--- + +## 🔍 详细分析 + +### 1. 已移除: `rand` + +**原因**: +```bash +$ grep -rn "rand::" src/ +# 无输出 - 代码中未直接使用 +``` + +`uuid` crate 内部已经包含了 v4 UUID 生成所需的随机数功能,无需额外依赖 `rand`。 + +**影响**: +- 移除 `rand` 及其传递依赖 (`rand_core`, `rand_chacha` 等) +- 减少约 5-10 个间接依赖 + +--- + +### 2. 检查: `reqwest` features + +当前配置: +```toml +reqwest = { version = "0.12", features = ["json"] } +``` + +Default features 包含: +- `default-tls` - 需要(HTTPS 支持) +- `charset` - 需要(编码处理) +- `http2` - 可能需要 +- `macos-system-configuration` - macOS 需要 + +**结论**: 当前配置已是最小化,无需进一步精简。 + +--- + +### 3. 无法避免的重复 + +以下重复由 Tauri 生态决定,**无法优化**: + +| 依赖 | 版本冲突 | 原因 | +|------|---------|------| +| base64 | 0.21 vs 0.22 | swift-rs (Tauri 内部) 锁定 0.21 | +| bitflags | 1.3 vs 2.11 | png/selectors 使用旧版 | +| phf | 0.8/0.10/0.11 | 深度传递依赖 | +| thiserror | 1.0 vs 2.0 | 新旧 crate 混用 | + +--- + +## 📈 性能影响 + +### 编译时间 +- **Debug**: 预计减少 5-10 秒 +- **Release**: 预计减少 10-20 秒 + +### 二进制大小 +- **预估减少**: 100-200KB +- **主要来源**: 移除 rand 相关代码 + +--- + +## ✅ 验证清单 + +```bash +# 1. 检查代码编译 +cargo check +cargo check --release + +# 2. 检查测试通过 +cargo test + +# 3. 检查依赖树 +cargo tree --duplicates | wc -l # 应该减少 + +# 4. 检查未使用依赖(可选) +cargo +nightly udeps +``` + +--- + +## 🎯 结论 + +### 是否达到最佳状态? + +**是的,当前已达到实际最佳状态。** + +理由: +1. ✅ 移除了未使用的 `rand` +2. ✅ `reqwest` 功能已最小化 +3. ✅ 其余依赖都是必需的 +4. ❌ 剩余重复是 Tauri 生态限制,无法控制 + +### 进一步优化途径(不推荐) + +1. **使用 `[patch]` 强制统一版本** + ```toml + [patch.crates-io] + base64 = "0.22" + ``` + ⚠️ 风险: 可能导致编译错误或运行时崩溃 + +2. **等待上游更新** + - Tauri 更新 swift-rs 到 base64 0.22 + - png crate 更新到 bitflags 2.x + - 这需要社区推动,无法控制 + +3. **Fork 并修改依赖** + ⚠️ 维护成本过高,不推荐 + +--- + +## 📝 维护建议 + +```bash +# 每月检查 +$ cargo update # 更新依赖 +$ cargo tree --duplicates # 检查重复 +$ cargo check --release # 验证编译 + +# 每季度审查 +$ cargo +nightly udeps # 检查未使用依赖 +``` + +--- + +## 🔗 相关链接 + +- [Cargo 依赖优化指南](https://doc.rust-lang.org/cargo/reference/profiles.html) +- [Tauri 依赖说明](https://tauri.app/v1/guides/building/app-size/) +- [Rust 二进制大小优化](https://github.com/johnthagen/min-sized-rust) + +--- + +*优化日期*: 2026-04-07 +*Cargo.toml 版本*: 0.1.0 (已优化) diff --git a/tauri-app/src-tauri/build.rs b/tauri-app/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/tauri-app/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/tauri-app/src-tauri/capabilities/default.json b/tauri-app/src-tauri/capabilities/default.json new file mode 100644 index 0000000..96333ff --- /dev/null +++ b/tauri-app/src-tauri/capabilities/default.json @@ -0,0 +1,41 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": ["main"], + "permissions": [ + "core:default", + "opener:default", + { + "identifier": "opener:allow-open-path", + "allow": [ + { "path": "$DOCUMENT/Meijiaka/**" }, + { "path": "$DOCUMENT/**" }, + { "path": "$APPLOCALDATA/**" }, + { "path": "$APPDATA/**" }, + { "path": "/**" } + ] + }, + "shell:default", + "shell:allow-spawn", + "fs:default", + "fs:allow-app-read-recursive", + "fs:allow-app-write-recursive", + { + "identifier": "fs:allow-read-file", + "allow": [ + { + "path": "$DOCUMENT/Meijiaka/**" + }, + { + "path": "$DOCUMENT/**" + }, + { + "path": "/**" + } + ] + }, + "dialog:default", + "dialog:allow-open" + ] +} diff --git a/tauri-app/src-tauri/fonts/DouyinSansBold.ttf b/tauri-app/src-tauri/fonts/DouyinSansBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..eb8555c6c21ddf45102f7524f93100172201ef45 GIT binary patch literal 1919404 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,=2zv{tJ>ms0g zM&O$^0^iE>JJp#HzE|xZatQobNMJUbz)$k{xsJdu@|(*d@T(<^5%_J9!0!VD<~s@e zF+|``jeoTfSP=K`Q3C&{-oJ`13afevtnMT5-z)}W7;8*otT~Ob);LDS2!?M6LpKB? zFpps<9#kwegJI5L*bYWGQ;;^ISXAp+7Dik#*TqQWVbm7_IWVW*p->?W?U8TVZb1`m`|1FAlRAAgX zg>ic^MyK+3$oDR3?^d7p6k*(3htVy~{i^@K1jd8q7!S#_HwQ!K0^>0YqpuX>acQ4a ztiK22sacF?S}~rL&-1w$gVL>3uPYmb8H}N7jF(jVrCyAe2ga18H=98XtCHe5CzMX#PnD#;4+bR;K@4 zJQL${@jf5I_@WWxOL3+O1oiXPBF5Ka7`ir%=~h9pZ)!2Vm8YKR#&?R(sQ2$nF!bLf z89xkS{MdpqJBjgAAI8tp{xXd*C!gQ6uiyV43mAF_G5!?(YQk7h{%^(pX~X!p17pz; zZCAq)}>RuK#p z6EqdGTw#cy-AFK;M=;_DV+5UEf>G&XMZz?}_&h?g+u9GX&SmCb&$#%d-e(XA)dLm*56<1UKv=nA1maBVpq?f}4yJ%xxpM zskED^_U7`*lgAdSyOsR&<(;qn={gQ>n@_Nyn&5Wod3))1=pTMVK z?<$Yt9D=)RzK48D3JLC|`975d_cerig8O9=++Xwk2MLx95j;Se1IGv+q#pD;;h^3p zf`=3d69f-cu0l9$l;Gi7A0bp~t?M~>l(kEZJf@iCrbJ_`>TTHM)`38BMS1!yDJimkB1^EOU9buZ_g*^oId*t8>)oUsv zcyX>Ek4qH0RJ`VFK|Noly@)=Rb%*@#Y$bSCHNmbT zf_G;K+Q&W8-K)O3bA?%g_l**~zmwnt()Y-#N9za0dq}Z|Rjap{;3Jg;AJzIXdG!g8 ztEVUAse4cGN%h%3MDVF9f=|~F)R`eTAfJIbg3rq9xh{gw_YxeeC-{Q!VjaPinS$~| zlLTLyC;0Lh!B?aoE+qJBt{~6XMhLz>KyYM+;2S*z-&Bpa%TlgloDE_iqM*kgx2aNl+jMe zS53&DPbg4C$WT65Kq!<=$kb@H5VE@ng%=1##tB6YLa`h|`VJCurAsKM_lQtRJa3p# zx}VUJ2|{a65y}*Qol!#T&JoH|ZM{Q;mJJeGK22zSm(T`Pgf^5eN3o4M32iLB&IqC0 z8A6+Rgf^EZPqnsCPg~X!+NzjPem0@4EkfJm6WUgF3sh@6@pq^uR5(UxC*^i7BUGgP zF7n=0{p}|2;wD17_Ym4MgHVaMdyNy?N8D0j-(o`hl@Qv$f>2o_p#w$;9k@WKJd4o5 z>g|vLLWeFAI!yf@-bScW-bc0*I@%*tCCxFTgpO0qYVnR2??idksF#yV37s-c=+sI= zr^)B^Swd&HgwB-DS<0WS_&L&^EA6?;HS`fWPko+WL+Ap<8fyt%*h%Q3DMBlT2sO#$ z;$cFUXl%|SbeVXUYacC*f@-%c61uXD&{Y{iH=(Q5$JO$0EhTh~C5Usa_}6t2YHKBQ zeI212Y6;z_y|l~!rcOdP3%B$W>X;;St75l_t8WjXI}Ad1%J(kqx637TcM&0-Awu^m z*R8tu7ZQ3v^9RMzbsc(0{tq`2>TMzPsQ8a6_n7+W)4m>;?g`a?5@DK9{|uq0#d${Y z0rmat6rtzy2n}`;da;<$N_h+|5PDhTE4hO5I!}aNtr2Dkz1BwPb=4Y?W@L`g8`|fa zgM>!23B6rF=pBpDyQ=lBYK`>}dQZ9cn+bi;M`(PM(1*fDlY~B&??jWJ{3msUbRCC2 z?GRM^Gu4}{5{3zVUM9>F`l5}{m+C?1jnGtypz$m5zb+P}`CkR0>3l(TzZoF(ZMiT< z=sWp*r@m*@%lEF(Md$}*<2a$&W7BkR?X|!VM;ckY&H2)v-m{v2UExxUJcnmY5wUZ;vU`7`( zW22byVoWzz=*HBU#7xX$ChIX%MVMZ`Fol^O!CW$cxpq5dW)0>#8Nwjux}BIy8!)rP z%aZ?kEttz1F_+78xxBM`FxQvQ2A&{a-A~LMd2dvUxp5`tCYi!CX0ALo6=&0V%*~|V zJPT9z7IO<%7{uJNTA0J!Y8W%W6LV{MY@__PU>1n8oqV=a{q5T^cWA;a%oD`lu@7@6 z`R!bQStMSOYV9(FxvS#4m1F9?#N1t4y_cAKOkwUReMt@GUKN;oE57$4W~utxSJ*Ef zbN>uM`m$!s1F|p=L{OcBhB3<(Ke!F^kZ#OFHC70RNppBM<`F`r{3_=$kJQiMsD8|& zdoim2?Lm?HDR7! zg;}Tk84l)|%AGled6s&vpTX4m#XP4I^IZ8ih<9F&Fo$`*Vi#0mHdbR^Sc7>{BjyU# zY8uD9SiN7O_@%kRB4+a_rp_$p<1 zz83R_V$2(hFxxfWB(IxQ@0M1~jylX+%P?oFhd!hE4jAA}Ig!!1}eZu4F>j~}aN#*se z)O>0H^XVSUX9RsCHJ_`-d|t7^5zH4egayo%iVsa;>RYJ!vhYe1=5Q6}tD3(i|JMgG zM^xjD3Cy<)%+V}i7E@;s^PL&Yce^mhJp2n4gwl ze%66G*^c?Sa$ihgPD%Hb>U^#B|K>2i$-(?K6Z5+Y%o%xoFYXVWm_K%7&Z^E&>gDHo z%sJ(LZNdDlAM6!%vBwjtGh7&>%gKHYmGjvHRrK1 z^09Qru>7@HfqpDw4l7iHWr}BYVcCmV5d+H+FWQ3@8^Vf@V7Zf6Nee60jiqyiwPXe> zvj}Tlu$IbeJr`@)1lIBrp$#ir+-!NSUnnTPK?T-^nS$my;%}6JwXw81XIPswVC9OJ zJCC(#C)Q?VSevVUo;Z2qSX-!%ExWL`l1IMk38bG+F9$J7qE&(v39A$+Eu;mmM1J?6)U#;5Y`@TSbGj&m8jNUwOD&MVeKQo z(srzUd$IOYe1FZ$IbqhB>#$a=!>h55D8s5$ z{Ue2=ie#4tgaG4>$^4HEw6jTySE0bTm9cBkNc*v?pK`$im-Yz zupYDoc|J6N^>7bXuX2y5=SStMbBood@$oJ!oms4YjZYauE7sG(GqqR)nS$caR$}Se zx1JXU8?au;#nSa}y*Q4wauDk!2kT`E>y>P*Vbytc8tb(otk-+7MwEX;cykKtEn##L z>uu$9?OX58VZEoH#rtJgA2eZ&w_|)!fa+#kmDzs_f4{iOBJ;{77socjGO6YF>J=gYBl zzOepm#`;Sh3$s}NQ7N)W79_+9<5lvYdhB?YOpsHFQ*!Nqa5sw zmEU+8J6F0*#ow$1d-F={JPUh^0_-gfVHSI<5p10m?5$g{w~^1b;uPd#Z>Rd(OS3~g zcA;`R)?)9Zac8ZIYOwXa!`@Z?yQ!z$4eUL#vG;VbOB%5E>cQT75_=!z_ASHSZv=aP zamz-r517L~s0h2fAN$~W>_h9ZD=h58Tb=b$1 zU{{NCd=2&q9oQ$1W1l4M$;zFg8nv3&PGFy^UQa6!#5sK!yH0yLLp9DAz&=ylv&yjR z8?n#M5ZbWM(Rhy5=Vl5s*bRf&=gH%IY0j7af(q=$ChQA^i&TGw>a3W@Zc@#Qo3Srx z!M;@d=5p-Ivav5O6c(|sn8a=w$G&n7`|2F*)>iCmGqJCe-}PEw--UgHv^T0odzR3T zeNzqg%^vnGjv#$UJ@&2YduFg7RLzGfu^(>0?#&m*u^;KfezXYtu{=Q@eOf<0hy6q+_LFtk z{o+662=aJ(5&Ic&2ITeZ2=;Rw*w5={Fj#^8f;?XoR!TEeh5eHHcsUdM6$5)1>{qj} zUsJ!Y=U|VNV!t80S%&>q4fbdW_S@Ci@04S|+k`#VkNv(h?=N7Fi?8<^`(t@cWMF^d zVt-nMt^0&M*@gZ24EC4OPpOZu+OhTCVoyu|%`mpk2KLM(_7Bqj*oQqkhyAlWeyPNs zYr+043;TC@%ui$gS&04D0QN#F_TO#T|5!p3_P>fRI@qfaIDp0o-j{% z%{jtrO%V2F2z`Y8!-NBigoEWmKjBaXVbc)Av!$^W3)c~jlo57Hg;~PUcEYhX!toly zt|f?@m?oT(r zv!z)-oA3sXpguQjCY;kmcq8RE?jyX3IJx3%Y6;S9HbZ#x9>RIzY*9{l%R<6ilJI2^VX< zdmZ6DstNDeL%2ja-7~^_cN5-cfN<#u;eE#m@1I4utctMC4B-RY2p=>>xO|%MAx(r2 zRgJ?e!iN_SK0-c~(jBEeAFUcyt%Q$-@Nv=}H%GX7lJN1`+X)%M7~vD;U(-qWBzd0P zK=>5-*LD#;Rr*sG37@9;>C)Df3R<73I%mo6tYO0SjfBr`B7BZ&o?Aq?LG>GE37&4iz9Al$F~Q}TIQeGEYOS@rl_4&mo(2@iG>eqoI8O6iB12)`u$E9y_* zD#NeJ|21)5m+u<};WzUMza`JnX~OSF|84={G4=Sq#t$@pSVj0F)&96n7$H1SEzA)9 zq@VDo+TUmL_)L9FW)uEAL+By=MLXdyvjpk&t`PpJnDE!GFi!Y?{e-6*34fC*%n|-} zg79~&rj_|H+oI!A;T z76|_>{y!Cj|Fwi!!i%khSCtZ8Z3tTb*GGf~B5TwVS+kDFT7|+Qk&FQ%z8)g}b|Qfq zB1SHeU9ub`}B9=wO&LI-kJW@);sUQ-qC!%{qBrZQ!NNAoMB9a;-;tdi>PY_vK zx=e@2I^{&xRcz@bk*ql)%cNgEN@V>qA{(?3*>H@=MwLW1@q{iSxyoj$3Y@H4HMZ} zoLvkeyUJ%bp*Ww&?hQot7$;IvCG-;6tCGmx2y;aCnI=-&Ph?-|_iHDze-n{1^>={A z1LbpIGm(P|gi#{p-9!$qAaY0%kwewfp|eCP#)urI+~MLMps49IZW6 ziGNHnkz-w9n8FQ&UYu=Znb6!YO$~Y85-RjL2!yo$e5+D<^VB zCXq9_g* zQ|xktIU+6MUztbbs$3#hTSQv5_tps_*9g}x5NT7N*9$k)5V^5~h|Uj@o4SeI+(+b= z9wHrsL~b1*a+|oF^1Va2Q~bMniF6GUxkq*GEhEw`&3!FI?w=;o;}UtWn8-uY_Er#i zq=d+$ZA2cMA@aDmIuk^moG0>>N95@mBG1TYV3Ejkbwr*YAu>2g|L9$%O5h-!}v5qU#+Q~kUp?@>#T$Js5+C}8EF(Q+~=j!Q;DI!x4`N|;jbtaMj$!A*pZ~BOQ z+d|~K2_iG<^#^f&R9~}-{iOZ>BA+>pzqS(jt%%6)Il=;wdGY@kC-UbMk-wDxTmAjh zP2}HUBCF)Tx=yEoJe)P!addqaYD!?&wadeG4mUwm( zPPhUmB9F)fj-yz#1t%t6JX?S6%N694=)+0&;-uu`sZLt@^dQcXR-CmJ%PhiKN3nIm zS*mgAFiw`d)|1z=8l2@-IN4dk2+jr?H<-cMP{`4GqZXWvYj8Gc#>s8M*|Z#Ivs#?Z zyKwS4aJEq2TPnYma`~#awR+h`*j9X9|IT(Aw-;vzaSMlVcAUi7*$_1D;^OS8es&Xz zJ)GS&?olYr;_Nw&Q!;_Gx48S1=+ByE;_RD;v!C+&SK*Yk;2fZR93)-2IOWnD;s}d4 z71cP0)#4mphjWB#SE`>QJ8_N@|7iJEP2wD*@mO)IGjWaw=Y%qx6I*d=)aOZKI43XQ z)GB^z4$f(nIHz~w)Jb!OJkFfNsdsSBR_$}@aL%2=(evIpzX0a~`88_1FdIkj1-xRDyG} za<}B;bg2Gq^1FQyN6&NT4#n;e=gvl)yA+yP=Csg}MS5W*ZjZcgFOgqlNFwS%8 z?|F@b<2WzM_r*D!mEAb{y`=N9_WAMx&MUn*!=pI*9^kxQgfpV~8`8YlkMq_L&fA$d z@5pb=!+Eb6M`wFyTs1$+!qK(tOpM@s>Il+&ru^g}&ga!QUsU3JSu4!rOwHnaHGuQA zdiYvB=(+Ap*Wr9qj`OV{$n!hZof*T?we9?9>CfV+m!Il!ewJ=71Ls$1f9n($aei;Z znJ*PI|Dz8_zkhJ_`v>PQ^{}9R{vO2nryb|tE}X>{oK+P#tCe3phx1<#QO1d`F++5% z45AsiM16Hc{jShXRM&jeXdxPGAQ~DaYPJ%!l(!YP=ZS{3j#LtL45623w2^2mi)h>< z>gE$o)DTUU5KTEmJ#oBQqUkB3OJ<1bcazafVI6s|TTXPT@=K?QW+}Gb5Yc6AM3GN8NZc#&YOZA~= zel%ZxTdUSKEkw6fy#n>UT{Y3|<-NV??l3@f$6}&86%yT9x}qMUyL1!XwVmj0$`#KL z-F=Mco)*!PT%voakG+eC?xWhJ>SbSHKl$%3ZCM@B14@Y=sJx!@QC;)VgL8-;;t@SG zk7z|1(ZhtpRqKcgb-6^(C?R^L;%9Xdt(X3sY@+8j5^WeIdfqtE3$$)5CwgHI z(Tju?14J(_Bzj2$(MvVIOkS5Oe?=?NmU*IA%HwM7qqUOgHSI*NogjMM0#QA;qc>=M z<22EB@ovr~dP_giTeYv-)PJW-^p1L>cPihd*xlnq@6mX#eD9UMTea^~{=PY)_YV=( z_rGY5`g~Bj2UX*tCZZ2l6V)?3`bay`N2Pl#OBg2FH$wDr>7GRBCE72qr{w>%^v@I% z9T4wX`8=0T^!ak4gTf1)L|+tlo9p&F`(VwD}?mfjmC?Gnn9zQB1`tdB$i4LNlNcXAgeP#*sL_b%(&&Byd zx-aGXWiQdGDnWgJCH~h44TAjt*F|)?UXb@U?L>7Sh<@8m^t(zyUNeJ4zb_~HLxG@r zKMoU}ttP79Pey-IPd_&k{iTBFoNCWa68*KC=x@^gt{T6q?tCrLKXQcyqJLHi(?tKO zA-doQLqz`;_wP}n|H$XxGNOxl!Yt8MZA4eApZ`2zjF|2fu{An~tyxBFElZdpmeEGc zS3%5=pj=>pn9)j1XM|WNTj(QZ77FvktS(}9u^?Tzhgd}MhwJ7iP-j~#CA|0JE(SHFR>lf`%bF2vv@_h z!VIxpRBzX6V!M?SD^|Q%zIz~y6WdeuOB#snRY6Smn%F*y@6$(2-xp&0Dz;xSvHd+_ zWd^YWvV;X<2TG^!46*VaVh1-6J4EqAEkQk0OcOgy^$r(~=qFYwk0U#X9VPD3>Y++K z9@9eX*k)qKbrGv>Cw6>2F`YMJCyG}izmsZ+oh;8&3W?Pg5j(Yn*l8M1cZ5M=b>f{d zK91GphE8HP z4iIY>|E39IH;)tRkms$f#BOURcDp#8(%;cR?9L`)cWK?#PwZ~to?c@2juPvZ?!GZ% z4;aLHAoif}Pz|w%#qZV6>k(-l756du^$ihwTs=K0ZofzDsVrho*Ash2eGK#wdscW( z^XF%Y4T}516tR`!4v9N7PweGFVy`sn&)rrMdsVvEVM}Dn-TYW)%c-`*pF?*evT%OH;+u67-@K7{UNv!@AL3ie zZ>u8W`JOOGeCrY7+oKk ziR--~zPmwuk1XPQW)m+dB)(TJ@x5z_@1uOF^7|^kpT_+i;$_MmAnyZnggN5n@;W$^ z_#rjK4{ae{(MtTVPU44;60a2ZNX3upCw}w<@u~&l$7T>ePMYd6;>Xt#KS8+@r-|1r z5k6(cUA%M`ex#14-!9Tig<%O&Ko0szVeOg|3cMW z;Sz7kC4O-+@k^?SUph?uGOaI{R_BL!%K-7KRR8KK;;r(zR=Mj`udR>x_2a~Egm}Ap zzDc>8RYTWg{8rVxZIXDWJnv{Ce&;ChE_vu$jNda&yt|C}ee!)kV~^@RB#q7q@m}d4 zttGDSeeuVOh(FOzyx$@IlwwbJ6Msh9XIqIsr*TmE7y5{=tROzrNc?5>@X8GFSG$P6 z?hzkR?#*1{Z*>xXyOj95sxc|16XEq(S_1 z`G3(ve5zO&CjOOjdgjN!E+qaxOOVgBG~a0bt+?Oi5TD5ww6E_wiT_YT{Krb-vzfvu z@t-07i@fGai2qtg{5SRUyCH}_Uq$>6dFiPkHrS!tE64EKz#KK z@&Ed9>A_uN5O+<@*XqK}=*IPF^!MTh8gPv|++Ztis0r7c#v-an~Nf&78wsHyd|pDQ=d$mSy5DcX0I_cGsW4-7pU~ zrxkajKHQDP&DFSRE$(J@xSLCpw}`t%2kw?NxLcLs>N<3F9lBeq<~G{HwyISiY$x6J zs#lnWyJI`&>lf6lQUctQQnJsuuTXc^%z_TjdD7xW{A) zJ-ElV;2!4+{kYXtxW|ik{21;Dowz4zU87vh1nx-#xF?t3o{}ZVyH?s$J8(~H#XViU z*U7i89ruhf+%xlpVcfG+w?0>p_UwAxbL4%l{LUT0Z5YHouMYS8N?d)zbM>rs8@q8Y zl>Q=dE|UKW<(mp{FV^~!B4G~q(ox)I)x4|?_j37P;o-K3bER;Ve6H4bwY*xDzef6N z%W$tN#%(LXz1|R}aBooU8(VPO%W-e2$Gy1|_m&Ra4ry*x{5I|T_EOx=9NarB+&eRb zY23Ty+trSHw>bA`)OP__*S&k6`nbOl_kjxBo_yQ~)$2o#bxT8I0Zbg!4YJWJ0`*8v8gz}%L z$IpszC);p8&%^zq757U+Q0=Kc+^^N+*K@ejnYiE7;eOkT`<*o3tJfbbVFY(p`JaSe z%5djY=hs=>-#y%U_4B86e|6*PIqWWU;Qn1KOyK_0j{9!`?xG`1;jZe(T`m0AOoB!d zYm|~$Gmpet4v7py7$)HxB;ju*5m4U97Zynb6$>e5N@poAGvAr}q zsGq_@5#1X2adqzUf z>BLckB#s^@Q8hr~m|+sf%KJEZRS%IkevZV6c_eB|NSq|?$?`s>K>w^kIf+wyNt`C` z>C)9rk~l*iXDMH=+}X7x&S@cWZYK%7?RdNZ;`%%iHx!e&QN8H7ow#Y1#4X}= z43M}jhs5oDBy=4o?o{lqE)reR+#}w-+TVSO-7mh*&WQ&bNIWz}Lg(hhBSRz}lkejh zB%Y`v(O*d7X;)BO&+Np2c+bi6dBq1EL7ErxNxX>ANn&LqiJ@u|FDd@A##c&646D|w z1teaR?`!Jeb$N`8ka$D(8{8$wd-hG?4hRgT&M*iLaFZdYZ&^E{SjC{cRJ8@1&oR z?++y;ew6MfOQ<99b1{iuH2-Cm#ILIT8zg?uATjR>JtY2+_78de*-zrHNRWy?D1>@Rnu=TXB<&)SVM~}I8EGWxmqx~dfO(gYho^;DdCJF?3>iSOVy*%lO zqj&P;l0K4a%PX^oIaH3DR$vK{7}4js73ABsZQQ zxrw;B^4(OPo2!>R^}j_q$t_DrZsn59FCe+KdfTRhxcaMCMd$y1)(R^=t?IYjPR+9UUk=#$bvOJOpD1VSgvb>(; zA@V$Ql;mN#Bo9}uh$5fKknLByx49VkqNLF`~JidnH2}LAN zEG1c^S~cqPB;`+5{*+phwZf?#Bu}d*d3qB`{kJ~JGh9KvovD7#8X;LfP4aB@e$Fh( zb2T>flRQuL&u=Dqf%uKmUMP=?6kpLzvZ;&Y#nN6fNb*woGz*u_k-Wko*`gk-7^PZ_FjB>pOW<3(1@1(P5FiwTa~Il_WdW!yN-8?^OM} z>PdFBlDxZzqixTOrCv3Bp;Xl zi8hi?ilgUq@@dt5Mtc|-CHd?k$>&Q*4yxV@>T6{#$)O68FKNFotLI^PzFJE1wO*31 zPm+8?-fuRLd~2BG+wy)#zVC|nUOvh9>qvf}o<3CUV~ga3d_EZ>`B^c^$uW{&Xg*ad zsODF*B)^v5|FoV~{+lwA-&P58B){t;InzQ?=k(+c>h;G)lC!-ee>O<|QciNNpX6`3 zB!6!vIln;i4{`qV1kL}FZb7{)$op@_|H%14Zmy|C{m?GsLCKVVWrRzAQ>o}$JddjRMWfhaMb4Y~^QW5DLi&Rwe=nSct zeB$zPJyMAbp^sFupH!-xlqaq?K`O1-l2%e{w~)$gAhnLLZZoN+g`~2?$(kdz-Xy7I zBczr~o2|O*myp^(y=|CHDyNjxM%AP?7Jn0mRIci5Dr}ZTYV$l&d6}fP$RV|5z96ry zW=L(V`8MKgtFfS-)OONtFAu#}rwSWM?O01{C)L@xlT?xByC}D-c)O`?vFh#ak=jGP zd&;*&b@ozx?_yH>)R8KcZr>_W`xTJdA5vuosRJ}Wu$Ot3ds-}k2N!g@M z&Lee7A*tGGQm59FI!!t~vr~2QJ5xQL)lRCui`3aYq|O;4b?ykM2Gu`LHT29*H5#NY zEFpD~I4k-|HL1R?>C`3ixm07bIG2}`x?+%2i}F{FlDbOsR_U(EA$6^4>l;|AO?7k@ zPu=K{YFF&0R#G=Bc8h#E#!20lN9y)+Qk{z5(L+k-@6=uLx*JmWXuMZHgZpwx-CsoN zfi_Y-bEF2-qesYo2Q_4SGMe3O;QqM~JoO&CyNWD-@ z>cwtSE5&(9oR>#Q={iomsy<$8B{h;k>Wv0cZ#I#7OSRtaCH0Pa7!&tBg&rcQd8RdSDqlw*Nvq9moLna(l__i zH+`hO?I87CHK`eKW=2STuhY?vfH;8( zJVR?;(_W|y&&(5M@T?I$y9F;?j2A)Z#dD;Img2>V@Zu$SZl)k^q6aV8hnJGSSB;l$ z!ds$o?FPI|Y1R>UT?=oiBTVX_!V-VIUc6-*mrIi^tvE?Jkc!a)kxFJ(VvR!rQA0Z*SGzrxLHU2yb7F`*q;$KZ;k@ zfOmj&2dd6NnRw;$J-8U}kOI6zTk$Gp@pL_VdUkudp1mV;@QzaM=n}lDUc6)a@pLVF z$EmO55f<@IR1Y|IfU*P``R9^Tazc&*Z1n~8T_ zJKpuhcsGpVwX5Du6L>e*;_1xobqKdgbK3yk?egtZADvTpcg*13hbQ)$Llr(t?%o`yT2Fj0rk)$uLtY!9;(87SkUj>JpG>CdsKL=2(M3mj|)#^;XSEX zzj}D82JdNUo+-y0XvBL~e$NfzJ+JyNGxjfcJ?!KWW4JvUP--fvp}HikDZ z{`@@NAES7GcHrrK+FQ{2?-{&*m0K*qTUCs=x*G4lYW=fuU8L6-CcRb;>5K}}dT&nq zCrKOGq=P-ALyM%XHqzlC(vecqPC4ml6KP$q>3BcsL<{MpBTSJ_b?cv?lc%oL^pZN# zYuA#_EFry47U^{hNiUT?tC;k9@>o_NERfE&NUz^OTJOo}4TYRh(i^MxCPHow=}pDo zOkSH;lFpN6i+a*q4wK$Wwe#mmZ#_+V+g#EG^4w0`?d7$De0G#}C-t$jIC|Emch#PX zGfD5>OL|Y$EKzQ6m-If;?^{J$->cJQ!=w-BAbpVX<@u!bOidp$O1dJ0^kFrmb^WC) zOGqC%Nc!k{(#II2kL@R2t?>j%pEyEV-@wu*wUa)%kMt>Xq)%1Pr^)~H9@2Hv>3uhS zRyOH+anDhmb8ASS=aIgkgmhy$=?j%xkw?0zko3iMq%RpJ-CRuivNqC}kLjQH6X!~e zS2vQrM!IWfNw@WpzM+!zjV|eSac?RjeRDPGTe?VhNO!Bo+d4?!K0x}8Y|?iMcZuJn zzV2=(eUJ8d?+oetvPj>rx(^6FwWJ>u|6%pmTSxkl3DS>wr2Ew8gnY{(t1v&Usb)=#L+jX^c&K?Wsn{%C;hha z@5+0uhxGdeq(7)3Jw8qPBh{YBApJ=iwepua%^K%OU-HKI!=)(tpTD-<#8aHIrUwC;hj){+S}Zs5+}u zd$sicRkDPBmaLh5`=QsTr1}^H{Q^nL33%1F2vW@p zf>aAwn*F;V!H$&X`3q8eh9J!coCO;NspD%w>RKg83-1u5#Yuv+WQ!nm|0GDu zP;U9lg0wOg31wGL7bMIHrM346(t4EN05}`)`zDm#jIjM>;M@ zw*rUTK#$ulLPFWw-a`6GkZ#|I^sFG=(TViAAlE!FHAIL%I825TyI_kx=G-JbxexsUB$~(p!S` zAmBaNiF8bm9x@@_C`boY?ApOM$X*$xAg7hfLJc{2Q zJt0WP@cbD1;@F3R^jHDX9Z0_m(&PC3aXfzlZF=H5q?ZNh$zY_(g7g&XdTJfgvx4+A z@Ot_YL3##dpV^4?xgb3|9qC3v`l~+@@;!$#&%Gx|&*SrXzS@; zU&P-p&PLjebPp2pza$_fAvGf*?@I@fP}fU<@iOwi+=;Xe>5w430ywW67o=BF=G9e5 z9}3cIsQ)$Odky%!hJ3G~>}x1{JQk@23FVLXA)&tGp9|9Kkw_&-hXv^klzZbRL3$H) zzKQ2=0^VDI_ZDz@Ya`N)NKYcYhXj~!qt3VYBLU7k$ny^1zVnG7y*nLgiy*xRxbNM9 zbX<_$k3_=X?{^}dhxC&meeeL%*MjtS{Py>k1?j^SB$WLSa6ZKIekUl}VPmu4E7m!f)(;TG5NO=C~vq-4tpD6p!45Z0OtB}q^ z`c9BOt3kR532;8IN9q$K*fY`>`2CAzNY^2~j06~8qU@LZk?{R1;Pw^1e+Bqo=OC>@ zx(5mL`BxNDGZNtb>k&cvCIbnuzj*-Zgdlx80qGbL@_vVB-%UqCz2Dt}1bE+lCrIB1 zBc&iMM#B9T!rcB}gs@EwEU4s-x@QBVg zOpEP^o}0GRwj^bF>Wqk4aSODasmoFpr!Py{-W|J$3SGUOkK@(Z+x0YFUA+P@HT55& z5u!;j3n@aTAe0k>(P2w=52)i0_~JmU+Y>ceIg_x z-_h9SD#i~YIeJq>sVy?{&|SK5rD(ISkk|he{X2bC&|*!-EZBu|P}0wqTuOkEJ*P zW&I|i4PVk{>1%>XNEIAtl*Q&}0fwBY{N0F#=p!`-qtj*g(-^XepAmT4oubp0Z)0eK z#Pr#N)l+x1msMn>)HOStlbUmLc2+i8X}51}^pR`liLnXA6C0*coz85E)CV?CcNNa6 zFLGF>j%&=yCfQjv-OY_HWt02bWAzUfjkm`p5VAhIG%+F~Qg2QX2;)KF5zd1MEaBY8 zIFI2^gu4DCq)9Xg0SxzKZ8(tBN0O%QE3W9iWX7$;A2$OYb^n)=*AIDhN?xPI>BzU) z4chSaEBEhT^`{vR|LQ*afGo>yz)T?(g@u}3eOlYSS)Q*!v1Y!X{S{!!pshw8b-}S%)UH22? z{r7j>f4`Df*MC5GOxTPb5nLEIk2Q6qZvOaVQDkL|7Ght#hD=^tCpG{e74p&Ndhl(SUv;sFGq(Y7a0k8{sVh30&Mhijva;hDg?^c6uLkv+Pyy(t*W|g>#sad zN>+`7)XBe)8y*Vuol;g>U%%FW_qc|_i9TngBv<-m<N~PzXUScCB5{dfezpLsZu zp$M77Ig+7D24oFn4~V22_P0eaCzje{rK*a#b1EyS6l*U_G}kvQ>#7?^X2-^6X2rzD z#Aap2#_G&wr^{qAn{ta2O@ArP&5Mf8NX?s(mz$eAGcPqGIw~)xY-zs3RX}dbh((#q ztk}5NSosHs*=#boT#4pH6Y!@(Ec!bT_;b1%b$%8gZyOv&K@jc zk~f3~hcRNEBsE!LVsfTb7EW0mw4|{3@(V7YA@iKJ#5i-bRGOMvA~`dR#&PX!xpw#N z3s^lR{nv`6^jYY$8bPpnSltOt2C2i8SDqu>Y*N0tj04X-5|Htmn9L<0IuY93mQ3SQ zrc6ptv1YYb$NA0<3Nn_-U(4@|Z@TRK35hEYJ=)dP+4<;Q3l^GFJ8}}@yQhWfvTb^O zWaK@wOA8mzE3Eg;PnSPT3JX0(!sT18Xlh7qN$u!(zftcup?C{pNiQE6h9 z7$fD(O^w3+VK~Y97kU=PkOmTkX=SsVkX$JJgucUGG<&ClQ0Hzf(J=@)h^++5(K(_~9@Y*W{QX=X@Xw1L58auT|l z(M_-ZqmgnYxnF*k#LFL%3Y6b)&-w>E`J<5^b_d~kq0Y=ST7H=1$}jbjCu!?r>*e?9 z%PcRWPYd`S;?W9J8Goq+%3}oL3Re+pr5Fxo+9K4{Y*|&)5CkGMHEcO&QgeD&jm7Gt z2@E--FfsXD67u&A<>jv8m23V4GEMHROiXIt)4nAku_|-52e)(7wg4+&X2-jop0iDU zzPFb+wgr+b`BiYvJ0w}nFC0gHk;!YV{djLL8VbC{{x1pRCuRp}Rh%Ix4glgBl?D}42?7tQx6_XQJ!Szk7Rs+be$_NLuAP zfd<5md_?VoP~`XJeE^(ks64Kh6agFeP3rSs@I09JJmxG6WDK#>Uv~Ye{qWW!H~#rZ zAT`KWk}mgW0DB%;hptu2ZACe5BNA2!TA6O$EWgsblh{aaAUPrXqkxfo!Fi1JS10;Q z$9jy}M+3bDE@sSTQ~Aup+oW-jr8hP-`}zk3)YWg?5*i%fYcbc9nv+=983YewS5%#G zc-_LHl8wF_mY>;TG1J7PM)V)AoAKd9-bV1Z)xwbV7-kr|s!}1%8++Ybt17Chwr$!} z9Uq^N@T>egeYv!F*}9@4GUKz@*plMdShXz=@wOO*^ikUa+m(6Tv3{C zE4q2#u8r$^NRa#$eYvD?;l#rHQ29+d=k?`_(YGemj({bL`5-5dGn;tq2sXNyAWy(l zkxvUPiKfi@j+R8T*tKkLb=}rYO(|7V`g9Sw%h-Ids{Y*brB$g>#=N@dn4~Kf6qcl~ zwN1^4)CI?|iC{+&EEUuli9U(|{?No6dq`&*akTgTAR^NV?i=XLGms7W8i3mjYM;r z(Afl>54NWhKDgwqa}FKG$isxs{W8sAbq4hRBJM*zj1lAuB0MW{i~H_4xN+;1x9?wl z_O9NQtPJUw3CjIUgWM;mKP#(29dl5Y$v{?A$EvZ5bk4!O`!4(Y@k8hBz3`1d5+;97 zx}i3HMvolg?esypaVW=turbN88~4FE?_Iq260~WG{4Pl(x5=-NY^5#CS71bbjK=^b zJRQuopho#Y7~c`35tj0sbgBEdRr4ra$?)pzzk!sBdPZ+1@%3x~m%jb>;>9z?mGj+` z84lj_9be3M$k8?OpOpy@>#rMxZ!7#3QjZ{O|ies|^qddqykPa%bvA3Vcp%iC?I znynjFZu!eBQb>H|-w3P{hMeT ziR6y0ZY5z&*8Ci6>-DTZsF2nF1^F5HMG7!OtY$xqek0SbemajKZndlP1oE?dKRK&0 zFE2GWrSy9rXYm=!3!U=)ug1jK?J?5(7i3RK3JWGAVtY+vRr%@|XF`Gr)(mKXu}&sY z?9js>dP|~F&)5(DY<5S!kxa@g6BjS;X$zcvFMX%0w5UU#MXv8CydLV0w>gvi2>fD& zG}!DgiO_0hfpQZ-(W9jV6o7hBtWqc%W)0*Cnf4W|n~KYJEU(RTj%$dEOPH}TFLSFd zs)%=5sZmROw^r1*R?DW1#bp_@65`|Q(`v_6Y#V3IzG8f0dUhgkZvyW9pm`V{H_QRW zM6KwAF-H1(=FRfy*-CQf+~5;PY8Eb%kAegK(I%4nIqnVcD?AG;U~s;HqR;kj?dgFH zw85RsBnH~p|80MPnh&+GIf;?AFP%6b#I?wuY^$lu$zWw(bw68~Y+I5+a~WNM8;t^P zign9|v=$CR8g<~|o-McUdo}Q%{{%YY-uqR}D?E+7s(tcwPfsAl{pAdXSP1%JW(&E% z9c+5EQ0;Q|)ox@HUPgC=UM%dHml-8(k)kv3(X%$dx?ovG=9)=4xoCe^bWEOnl+@%& z(fk|ruWqFi+H&(|4$v`>@c`;!U8I6$U_nVb2c!hNd6Tm_tWNo1Rz5p-CZ`eSea5F@ z@Qw~xfS#GI#)EDoM$^wiwrk5~S5CI8XNxPF4T0h)ZD-eSeU;74)jKev##e2_5O5?n zT+hdR0mgiw*O+H?WX%;-pc(*WV}3@$@Im#P{B7-j(Wk3hLFn-`{P-y2OEApN3=FMvZ z;@gS5DA8IYe@XpkO&3A$=>D(B5wr`^QpdcyyjUc3IP*_oxUiw)$XB)e7YoMa7MG_P zbHmzJ zK-*AWhheSQ#aya!0bkQICo?(*jFCHMJp7!p{P034Hjh-hPgJ*wIl0p~3=!ji0EU-d zCr8h__bd!endxPDBiYUBf?lWJA>VMlPQSZk`|PgXi_e)fcjKICtPE+F52M^6(kWj~ z7P7L?>*Pn2WqwFlSkMg6>_!*);f5m@cP+Z`mLnG}Ttwtw$pgpSXLj>f#zst1?xX}8S*qm;YT zU3%ca{plJ5ca)R-C8-$^+T>(yM0#p>T6jccL`o~shWgB9)I5BAJy717iZp4BA9_kih_qxsXdwbrBrmFMu7z6 z=Kwn9%up?9O+oeHY2B&m3Pu^JGv|_!@Z7Vz611@iu02`dp}@gnU9dA70%I19G~}G! zZe@e3&ddR(u&Fjab5>n>fn%H{-aaX>wJx)cT)nU`HadH0PQ1kV zXKw%NqTx(~gC*QKxVe{zeS7)aOE$t3(tXlPT z{GufxR*$x3c$J~=e#aaLmVgT*!pvv^V+E#@$F^2RCai0W7tD%2>z8Nb#~SIvN&ck; z^D|R3rOL$eDgGDs`cTaxaYctAD>*eXW2G~7SwVhiSX_H@S6cqc{OC!LW1Yerx{%IA z8!$()vKa|8Uu-T`6PuVYwQ1B~^jlR^LA9FvXhz}q!h)(AXJHGRk3}u`URA)p%LlAh zhr@~&d;g?pd|_f@A$wd|h!Pe{;rOD0%Bre?99A@w**m|`^{`7}K{0-{XqYGfMbT&n zYdN=T*YDkMTjb-9pYy~M`r9^KeDOsGVBvn+oowyC^wRFc)NT^vv2!Ug_J2X26Jz1w z#Cj3Ypkq9ab+(|wgc;<7Vh6Amv43bWcOx1NB4abQ>cTu4e)Q`2f>Kjdl+IA_=fdE|-%*)U(lc|$zq?f5S z00_XW=LfsG;P0Z&j*boiPn!FG5>4>6g~9t%h*n~lvWBu5vJ3e0@9Z~ETNEDWV;Y)S zk?m|q<%uSrg$pmgeBnYL^>ds!apLC96DEq{#0i@>Pn>vvYG(74?9A+(wuYSa7`w%w zZ*h>hUIlo;&6CIq#)Wy!P@Il*rhTxqoILF4^3Qic9 zi))M)k$ciCW1=m;p3wPVrNv~7vQ1mIqBe_UkBM~4<>dS%vnwUR6nrHqo=8${KpFi} z2Z>xkzXYve@3NIfj}5_DiTm{wRf%;(rzOdfb_yyaT-oZG=NLJR;b~WB~Q{$g+YP|8r#+w#3-+Xfu z|KEHwVDa@x*dIsnEgW{UKx}p+30Lgq5wiv2zirc8`87-#LRJi$Gf?NQUGfu{I8?ZQ z88&YiSqB!UPf^FTxtrvhPf~|mGO`L$otLmSL@^tNwLy34*$HdNrKg&q@b(-z*)#<; zDRZI0I^p>rqt0`-O`H4aDeF868k$a{PCrO)=xxk^8gE!Um0W@k&w zNvp=(3UiY;IghZl5Kxynf46~zTFg$+@$O@6c0z?B=rHX-KA^_(U5x&0;`!W#d=jJZ z!_PdQ4pL0%FU8Uv7-2)K^Z-;D|v!s6o$dc7eo-e9TGm;gIVj83W zhQb7IX-0#2Bl!5Mp&-GykB$H-s_+zjhM5?XRZOFehUxcQ%gOtP!evVDiV=Xl8VTXYF}u%NBaL)qOkpfsJp) zzrkc4^RTkfG;(%s#Tc47WQGoga~ER9?>$Q&gyqMqKTk_Xfa_IT7c~?jYmWQaFqj^> z!upKM72ap381HJ2jXvPfRX2@3;63An)q}Ce!v;nfC+9QzH4NqFt6L|J(T95l_Pf&aqT64bcl-{b%w8EFm$4yuky%<`96&Sn=spx%QZ2I^LG%_IIPc@ zBs!Ti;(gaSnn)6(TK9E~E|fg=)C|^xxICA5ih2l>rDdbmQv+Mzhf!-9tcUeus8`RN zs!1CaDrrZr420S>YGshMgY_}_!0E^IjpkJC(r?>2_pH&XqEHnlscBFqDDs6_aU%o* ziGxPlGgKOI3uLG;@c6$~U)_LTjm>mN_`gROv&;cL(vD>?|9Ab%6__8M$H%W{ELJGJ z%7-np3$U(E+sFpWWE1D|DNG8{tJ#{#|6ef7`F^!qPO_ zg4j0N%Ad1UFdcJtiDr@P_?!Es7ivh{`B-r~uxJro;=Yl*&*_5ve!2J z0T6kNf@jU!20u4umt(DZGau7wt$YW+Gv?a&3v1S>`~us4v{(ezo<9;x;EA76{Hh3@ zv5>k45H<7TZrbd=8S9={E%o%ne^X2WA}rLHg5+T#1>k=8mf%lQ;;k_s*1-lRYAQtJ zRX15BcaZ03>xK>To9oxhSTAAt(INeETD@{wJu+s04A6$fO~4Anx(ACRG1INvKe zot3qDdXij*s$z)D6!ABV|u4(VZP4xlgu19Jd-PiR29IMJbP21=Gk}Sn zvO{Vp!F3uxn!i$(O{R*Bv^lfVGAm38vb^7QCf~GpW(KQ|>rBKcvh`<+vO)uy1o~PMwaUzjYfg_(m@OaE{6a@;OmKwN zoc^O7grV5fQdbLnH}f8Wk?jz<|pKR==3OFQ8%cBh;6LNGQ1bJdYmyT39i}AE8EY@pS6O@U1-~)Z zu-#xp_#6W8{YXiVHErd2t5Qh4@yzh-g7}+PU)i(g&g);jLdBGI{7W z&-xiUXrK{_>u@mO7U8ricJnCJv^R*5Z5_FYgKl5VZ4vREwe zurnS)JTf^0eG2Fh@VIO*Tft?~b?~EL%)Vs2zW|zi6`*6~`p__@rgF zR5ivrYz<_6`t+vKrSnbuOOs5#Bp}vQj(rYsokr>^nqN_@)RoykgM5#5V?_A*+TccK zEA7l>TF#>E^cWIK6d1u+17 z;H&Xt5%J(S3tt@|*7oAE07PPy7IrTwF89UPg2KN&Lq&rjKhqfHLlxT9xL7gyDu+ zKh|B9A?S%F0Z6^2sV88vvhp0hX3(QQu-#3Je$Pd+Wb%j?YSpAKGnn?-VPUSg;-UIOp3H^xvZKS6M?Wp1T| zu3A$N9GufxK0YtAZYlAZv6W1mmYtK5GP9yFBm0Kc(a|?*3sO@>np3>K$(b8Q^pjh4 z;_6kz7?qY39!^##n2R=2zqU!hL77AR_s0?Nby}(i9R}CIMa;w5PA}f)RF{7Ff|MRx zT_E|nS?zp{JfB>(WQqI}X93_=!(5U(8;tSc z;Rxi3v#aasDt8ZvWK{>40GE4EJkhi2x(B3$&ENsf#f4Y=M0)Cj*$I|WLMVb)YAR-V zf+%?3hJg>xL;JDn;fVwAw4JP}zkEX=*}7c~0nnh$vJwEm#t8OSD17Jw|FQAGHj>x} zJgqf7(NV=KYkMn7i`Q&&IbSEK@|(4>F$XW+vSII8HB|*JRL^b0Vp@XuX+2gvpy`aG zSQr|8G~qGws)h0IrMc;;IV*Zrqpzl*M^EmV-#uetJ z^m4j@!S-cs@wPeGdcJCpkkm6uYc`u>D>hfgnl{&zp7GjK+N#F$KK|#Vw4_k-Z&?#& zNtyD`kM}fHX`fQzeo0>e&Il;8GHK|M=Ugwc0mqb=TA4>LGtD{BA7lp!NlMCGknb`? zS+}J}86qQXi8|euNS)cHk2FMOY_mq;IHQh?BJzi;=%mH#Jm;$ikT?@gF0FxK@5`c}=8baH&-&WfsjH@RB=U{iHmLVR4!CUTp@ zx%MrJ%4NwIRa6`W_o1gP{~P-VK*Lkcu>~3jf)bnBFLZ{nyJd_rCGh9Xmc~UDUea#EFfQni|PRj8A!=0UqXKrxo*$ zbtmywy_}40@d%8$4Oi>y=l+wek7h-*{v@Nkoi$AlX1>@Zk1iEWlkphlRTn z=K4V0*#AU6fZk6Tj9Z45SEFp0$u|%Ng1%H4*kYbiS7c5yCuB7@+hXFR=(L8G_!;f( zB_-1r*H$g>(l1SrVqIBrF&m>}(ya!4%yO&CHEn)L(Xz#*#cF%iICvK4uf;Ysi}t0> zy(D=ji}+{p?+6Qq_Z@PV(Ppc77GxruLGY2K1Xn^j*qD=6DNQm0^W(xoHM4!w(rT*H z(tW7{PCTnHG$h`q%7$0f@9VvRD|G)ObNN8TQIvr0Y*iW2_MO?Xv8w1vAmt zET);x+;Qz0YiCTGzO-!%TEegDb-{yZI?s3G(8-fi;hy)^Cr={)^^Zr{)8 z$K12|y+IkHBjeVn;XNrcWxUB86Brbr|LbLI&H&RqG%-GADdN^js0KrH10?wWuXFw> zkdODF?T}|yE*vpR*}k83RsO5|kjXIsP{oW zDqnUVp)=e^l<~MuJRAA>SoR>P6cTGIfv0=?_WQTpk7C#T!cfI-3YNG`3Ix84&v7mr zKryR@>p7@eFFi-7qUbiA7_R2#taV?jsyVK&5x2zlCWSHyH>fr31TC zrI%K0{=IkO#-850)@~}U2#-jc+0va5=P1jhB;G`ST9KAKGrOko z{Ie^|>sxPG*D~F8hI`@RiK#YMXvvg(7i1M%1HX^YU84Bh0V4r}V!&O6C^cpQCgT8u zf!$+GSKnWiX3*!`+cWG^SQy1o4h1G%gkFys@9wrZ5@1fuuE&<^YdS+iqcX)OHbiJ+ zqr+kJu<^_4TS44x9RyPaFbG+3WD9XGmgiHri79R=K9hz4A8w;5Qb;k;TnPPzy}@)h zd9~DknXml4-!lKIXdNL_<@c_e8XhtC!;Kr?$eUELX06;#vT$8y1K|Bn?D1fBT|98+ zb{&f+!omg|7?=XX`*z^KKVIE;JKK}+j{^_&1d@>7-pA`V`NvN`{S@?whaI;<@n`Y< zXY8~BN1nr}@kH_RLBslk%~#?RvP+w$r>`dsHb-n@RZC`?k558OagoI)`rkc1aK53q zVt$c}8av~xiACk6*Z|^dN-Ev2()}CI9S#@x;#nXQOi%Jn&&_Aze;}!n{{r>(3}G%> z-bdfio8do)oYdKw@y0xdY`TQ~YHHbwr}$^*2DJHFlS_(hHZntjL~pj+i<1yH>pt5X z<{$I%gn%Zmf}Zf-16PYa9O8;ONzCNcZBs9sTFv@=N+7v3TK=J@-QHHdcIyCd(OQ&4 zj62GKZJ7K)Q~<5rIblZImIGaj4g``Zue~ncME(E$?}0Mp8er-XDLyYcGBP?Z{?eEj z&`s0u37u;yXrS`Nd8=#pH^0pmM_0Sr11Q-in6qPCg1*(y%J5ZyWkEP zVs}Arl21#0m-)$uw_LMj?w32@O#~Ua3}tJ9k0HNecPZ^3w7VSSu^wk;Q&z#2NUb>s zaNQ^F3Qx|Sch@Z$y716{DYAgxt<*;bO)f3H+kJ4$<}I6938gk(BOAMC((jRf6q}3Q zUFN^k&;6QGPHz8jqgP2LOPI|iplm3YCD0*?Hzgff7)MZWe^;{Q(}go~GBUGgz1#Wj z^aj1oOyt}0vRYd+v%xp=r`szM!%;QYRrF%OVzIrjxqKnR;0qcAiH!z|ex!yT_d`7k zRG>YR8B8cA)+`n9zZ9T~ta~3YaV|fT0HZ-`+TcNN!%*y0AiPhQ96tm(j_5Bg$6c^0 z8NVr8F%));FR&F&(mwup*c4|>ww91w1-v+^Tz_a&@6m;YlW7`*kzsd_% zK$OYuLFXkE{J~3iq5UC>PXyu*F+R#kBu2Z#qAW(_w_KX@`1Y?mq z?|d?AX-j6#SG{6sY=jnZHHxlP;v-|(&JRz78)jvB*ie=A^NPydSZb zkHzva`$>uA8*4`&c^tug|8=rmeV#bKlke<9MR17O#IMLDsFz0?isCoFN03WafF~#! z-@k+&5+~vrBs()+u-afD4f@{EE3;0#4%5Ft_M zBr)PPauufs)3=b_@RRfOc^`STkG$ANuELQtcmaP6Wc^n@shCrE&PS^iCJJ*<&Tse$ zXk(v|W`^yeY#M<8;`_j?UZo_%>U7?<*w!rh{j~=Xcl4I5ej$0$b zxio=H=6h-(sWg^1&fL;MU%nHhg3PGKv%5U|Z3&wgBawV$nW1F6lV$W3F2ns_6X=tO z8f51twF>hPBN0H9y~P29>rPDo;l`#dK-pZ(&&}fcR9UhTRT=JapL-68@%%mf`BgYy z=CxoSpOAn6l8KXQe1m;`gXNcqBS7O9k!HRPy6n+OV9!tX88R+stX> zTi+b<89`N5tE;mzGMY0dIO5_1UU-GlSDp_@h;d}b#3zhz+`Y867F0?hdlsxLE{@b? zcGieAS}I(Sp1q({GSHAPTl+j(Qqt+;y8uoN5$D%^DNg2ldiXA};k$asSm)`Sbk`3J z8{?!M_d6%u7bM?*@>6(lB7oPXA<)wY{jIg-n?E3uy@?pUAOrAg7bp<=!QZ z(o)Bgg^sc^$HJn7w6wUAzUQY;$M~_39=eD7h9c4ZFitRn*h;j~#g_8$&q4Q0%AJrm zsk5Dab+?^9=-@f2yg`s#7=@5E;u;|+3;>Jx)Lz#PO*?aE;|ct#0}sks+* zaDBj@!cw-AJF(r#hv#C>Al9dEfgWLMKDM@iTx`j1&Y@Uy4C>1V4 z=0PPlNktt4r-&%6U@{3YhvF?L7og7Ml#(pU-cG;leS@Dq;(na9^|?)>w0_fA7{h4uNefQ0P>~_Oh|?1gC~joQ+qg(9O)$odavl|AyLYi4$7$x%2_ae!Y81ak5_pe_qhTF+YrmNK7-?d z1+JdhOHI9#dwO05z^gp~2SF-eL65%xZX1WoD8hV54&OnJB&EG{zq0*Z?pC0@$|p0} z1+Rht-~WuZvAB&vzNbHq+%qo=5n>k`v?jcO_VXjS<#|G3zv(jXKk{V`9Z z+IlZIyaS%@1?tbM;DUO3{^B9Q5cGKy{FrZ3d~SfYdPq$yB6}dKv>iTL_FzwCFNyDc zyyroFI>3p;Jv~=>Dq(acZdt_SKqb@wz3)X`Wolh)z6typ!e;Mt0P^{<9zkBG0eH^% z7!9pCdrO@G3d{9D$2*N8wcZXBJ~)g<)dUU{#tXHOTjo#)jt^&T|_` z6KQIAVL8|veS1^CTP&xKv-~<3FdF6q`oWWhWH;b=p!*u0mqob3cz$pAHtryI5gM%I zcpka+4bLxsp+UZPIBXY-fOHwjP3m(pPC@5+pvLmvE?6-i!EF*OCWKj^+(7~4nP(Rt zaOl~^LixgNO)V`=+sJD9oZ5^MN+aE06PK-d^OV*}2M@NjPEE{OG(F?Q|#74_NB#Wci7%|7`j?@-u%JszgLNMjmP=dr7ri z_UWfMfavJ@^@h%FQb3&DomXCoIuK<=|HWm!fTNpOR1@9Z_4D>E3vm#YyNGO)KW4a4 z;Zor;F_ZiTZ4H;9$I9@Eso*A&Vj3YNk2RXLIL`_%{Gb=c<)r^A&#Eu5CAtdCHdhNy z(adXcq*&Nr1r1r1(J3*p5ou`=u`#Lgx9JhFQc6rrjFc+H>xo#NCK;=14U#>txjD}s z6ID}dj7hJGiVn}u509RUBuQy$QgjTMmg&^I9tfcZ5bW$mfRe9?jW`?agk&>knuDnO_E4sQ=WY!`u4o>4iDZ^L~eR?w9=Yr zI6GMXUxkLW(RIvsZp4wkX8l#<-B$TBY|5tV%H`C#tY0SgcZ>VQHnf<}ISD(<%0g?` z$YE>fi|k!&QzxE7^*;-rv*>8?2WKBXbk18`o993w!yXx$PmwF5*$G5=60#zR&(}DA zGU9?o)gV|gsn_S$+i7oW%f5?SCy-ll?BxA-5^^U#@4K+Msi|3MCjqp zIr?+enzu*h+AGTQ^CH(=Dwk)*ABc~;BrY=xJ@~!QNl(z}yf4|9XIfL$AJo}b+DA2g zbh>hSD`w22=I7b~WypG#yNu`eocBuZ0(_?mUy;&ActQK+{5ZIBjd>Rk_DG@%S$>eh8v%_ivUx`$vOcq5l@R3;38IWo+n-V2k zVM-S1@XNHAOs1IRg48T>ZaFc<2m0GGTgnn*g91`A!RIByM#>J6ipII=@H2TPVT?^Y zYZWZyRXpjF!<{56wIDeLwOBI!I!IPZVVcpDXf~w=3_P)z)F&y?i8xFzm2|aarUV4V zCX}^g+BAU)CL(`2UiqcGHPhxF6rTVuw=f?Av5Dgr$JSrLDNOz;@`kUbc(B!AL6)73 zKtA(NW1;=Vv=ME`QPt{cxm}<_LZD`l3M~(H$Um_2b6bQG>;-Z%o@b%SI4vIur{&`W z5|%DWUVb3siGL+8OCS#`ePro|Ag{sSO35 z`uVvH7k9`%@=8&Tox37X6Wy!qzf?{Frfg1T@*D=tJ~t}|0{@6x@bVqFbOf|1J0!s7u%dx3eHbxXjV@|>wQl~j%ZaIYeq!SM`kUlq zwTb16Zg{bI!3F0{&~DE7bIU$c>u@;4mu(1lloOkSZ5%afTsGkFp$dWn{CH6vkr;?g z8Mlnz%mB7$n?R-A{U^kB$Ov|336l{J5A57H4I~0vsNx~k>^u7n&s-kuulh~MPgg8l zS>1ics<&%=;Xe*ulmSK!+dc>m@d2`UPOLp zR=kTfQgwNtmEdkAw0vD`+2rMa`qT2sWwE(|R?EEofr0H66=`L!L~gB~Hm!PVWJz$E zRHyNkq~v1Gcl!Qq;;UjQrz5$_{$3(qT`&)>Le zrH{|bRX3`iF}o*sZPVohFdQSmJQrG>?r6+Zbfv8MQZpA^fUv6?|!rZ?{;**TM@ z<>DNwqNwPsoM@w_6DcQK5`30NIdPgcU24r3zz*F_Z-vwr^hz=1K5yiq=P z`kFP9e?KvT#${vE*y|`Jw(x^Z*%t2@jxo`cD$<(r;^O1uvGt|GKuZ1@lvLcDms{YP zv285GkLB0I#oG~boE<~*ryr2=a$9CNoWp6GC!7YS5O18BiBnE=P+y+#XvQyNwuA)j z)F;;3U4^cxQ(cAjvqCeN-JY2##Yiz@HH98|DkIjP9NwN28xs?o zlO2N-eTI$K(~ui3Y(@b4kBRl6Wk+SQdMx}FlTT;W0S#E;-h9h^=~$Z>ONNOo7z*6Y zXM@lOW8G6=ROcO>{LV3(kRZk6r5f~({KiJux_U!uUJTAtoi?$ckQfaHs}*Ok7?P8W zhA>IW$c&DbqEkA?YA^B6OSVNuTFfbRsVONb<5J9)$jFrB+$m}48R=vuuJSM#lB@=! z(ePHfgwroGGh-x7P)71z#n{v-R~8&R#L-8F;l>Ge+`MPXI3@vVhcVt9Uxn{t#^k#t zL)i|KrcQwV?U5-T?bJ>yA{z^5~&BA2IHENM&Gd#d}J zjwuV<$aT@-;r^i^;rUW{n12Wr{lmuU&3TRGmt9fQHoo%U;mT$j>0mlAqcS*HO5c2j zEhjigO5ISHk`j%5e;8+x{WproMI(H$`~(Z5dOwaf(igYig!9{Pu8NefxiMVb17HIYJEo;YB*9$p^k;->)--58T)NVLq5K z<{dnPxN5PpEnTP_<-t+Cv;r8{YS7akMSbDV-au4XXH+&)t@rmX>$s z_T1FeoSg}}h`?}B42&o%i3k(LfC#kmbm&c|qY@hI`at&9g zIsbC?8m_zpb$NLvn8AbXQ26;|vPji-qbP{q-`~^Sdj_dz@1t`B&1_`Ym9J7hFn9OJ zza%!+K_$5A**qJO$xQBeUG4=h+)!OHNL+DzyD>_h_G4NEqK|#Daqoh&uoq+8|809QW|C!7gtv=IdDomPojhd#{t7P zOTf@LCEaj$+R(t65qGBz>2JlBl7BvA$Y?96tgc+Nu(GD)s_=aHD-#?JTrXfcCDL3k zl~pffH?37KS8rNNEKzP+D^9d{+dK^a#8YGMt)GU{Ri3>4Bl$h@=Tl|$HUyY(mxG0ogcKQ4HXi5yBp_(8{H6eOo&V-{(v$Y=k)9l{r0o(t&JqaW*!>Zo- zSkR3-9~Whs2L&YVndvTFJiBl?4GT-iJupsDfr#Y^$6D7xzD~+wViYCEmP1H5zmjry zpXXLe`OH3cx85^6Kf9L_K8%yzOvz5Xqc@FyJLR{1eWT2180_bEO-;xd&O3|1eztCFqL4bccJBA@(YaI!&e?88dkz@?QN zK@H~9e@ewUo=F}hV)~@B2pmCef9e!-2YPm2-{?132BR`jcucK}-L(jR(P_*ISR-iu zDU3j+Rj0np@*ljZXzMH9yrN1{9V63Cih=)YAHUdA4&nuTgkv0E&)t^Q@VK)xLjHi) zXt?Fh*6NN4|B1m)mh|=gXSZA)Qh4PtmG4#BpN1vQ8K~qGw5L&hW8HZ987S$4_?s^g zf5z^qHNd}+GfXyNEms}4gB}@g)0&ND>h!Xu-Dj=u+&G_{7-3SAqCQ+;>uot-Ud7B; z*Tk@q2DBUr+_2^by0F+dKV=N5Ya3YAV(t`HbtkuxXYluL`{IKVzX}c6=}D2Gzz|8KPCrCDAIlRJo3vkG;3+IqGJa4j z9ZlA6>adNdd6e|B(YPd6B@CiJC<})y3%);J7R>80}`Y}IH_@`_Wj9CJfiFS%P9;w znxT*@5)zGqucHzbon9br_xE&%OjEVtag|YYsKXpJDXbqFB zh2Qx-;n8O&&I`h1J1@c84x<5Kp^2^oH971S@UT!*&OvT{$vQNNV64}OpYzie_`Vw+ zpHDW}(N^}%if5OCYrURbx|%&B@AP-m5FQ_;>>qo_eyOqz2hZ-qGn?15`&N6NiGmaL z-NozUF%sA*X0fWt`QCeEf|@qi*$Xv0fx}%Khg0UibTxY+?&$S?T)Kq2UOX`vfQ^H| z_$|%kbq>@n#=f`iz4zoh-g^)E??Zl@SAI1w8;R7$fg(H4%nNWH$VM9;9B2dLcvvf3 zUMc&~pD&2N3a}4aF!85$Um2@b~H>*rntk2@dD?QVb3pJE!WZv zJb#!!-@xMS2Flm09xSgszjXL>PkG!k0bT>WQ6B5>s9&HVc2Z+^|Hy9T1fTNfyu8RB zo7rJb4D-08|YhdkVgYOA*LBj2Igj zR?4^?_4%c&HvYVZW+=F#y}bOxt3fO8@}oUh%Pakj@+>lv)5D@=6ah7OQ{-gvbA=|X z-%pmW20ftWkuoULK0;kos9(hPrp&QR^RUzrw#BI)Qz`;|c@6!-7oFx8z+$&$047 zyqZ<(;dc*?EV`LP%t&Bk;$bvBLiSUEs=V*X%T%dSdje}Rs+aw66Rd*3U|!xH4^&Q@ zk$5ux;e9l+e)nsPoE+Ifm^|+&_1Ca5JS^`?>i2FxmsiNgZmr-Ffr~7(p9w9`UA{v9 zVlKB(H_NByR9TtH4^S+L9QDw2FfU9s1uBk54h54kxC+IF|6?F|N1T2gcE`x)C-BJUKZTx19!_$g6Pin0OA>U$c4uPgRET{Re6n z4N!{nCr5$NK|xs=(_EI0T}B-Sr(1^i_8+W=ml?YrMo~UE6q25HdlZyTp9Z|=R5_$n zICgtR;`RsI^9SfQv^`_j!$!L=%Z#M;A1w0++M_gs53vD_!Sq{a z|1s9#B&mvcby8wH67@S>@k@EqE zvYbSmkIh>+rs_jHqh8NoW#Orwn3W9j+q4Cl=DYvQVQlUHx44vk#&HRt*kPTY}K%6{N=d&fnO85KP;3VQd==5F@lQ4{*)=2h7LCm#v)ON0PGCM%ev>5TyPk!Vf#>Q>ocw&*A{K1D z=&~!6pzA9xZg2PTX>Y&y!g=g--i6)u@R`?s!A0%!eDHbE1?}_N#JX|aiyHA-w5YKmhL)Gln^#dzE34+ruBznyGDyP_ zEg3?Xyx|5w zl>5#?9N1a(_1EcHZwl-V)7R)L^mUx-oCZ%1E<*KkW3%lvo;y<6b){aBM1G`k|K!4= z%F0=@Dl3Z$C-2{%aYcT9Lqk4Z?7cbTdjGQWsd$JiQ_IWzug}=DiQLk}=8;YOo!9+3 z>OL-*nD3YHaB_BFH@WdMG_DJ*_MO!JXJ%h#U0`2|`+0 zIi97bl~<&t)_Z<7ne{lpg9-uo$EsGBzBwp*5kmtw*R;|hknKO@vu_Z$H;c%vpo)=sEds-v!AB+M9*rR2Uckd z2??KfNrq$Urkk4Tb5_;KpDx|=*%u_nI;k?sXu9`?)Y6s%%Nw$-Th6*1{v5id|9Qy9 zAJ|;gSJc@>ofvWW@S4Mi9b`Y*Coh+mq5jVP=W+Hw&tt?5)H<a=WRlZ8 zZpBSbG3}^(Uo~Csa?j?tK1cR@^22HbL?ez$1Kj=c%Pc?r>GwB|(&g3eeJ*+(8|Q?c zr{d@j%4o4miXAae&-<@lUyHdCs9x$md*M8Ghc%<~Y~YG~?8F)#X4CawCo0VrABJ(m zyoG0D_be6O5LSpJ{?(rdIvvG3Pk zckgzLqkpPiX_fs_RT{lO=F%V8yuzw+xU|@rL+1XvYuB&3ajR0vuhx~-tR0kA53~cm zB+!a`PK`F)9pZFZXf>(qO-XdfeHH$`)qQB!!QZy8?IrdQ#H`sb5bmn!j`*uA9dBm4QxM!B444*zxbzRXwq;CM|x- zp>>7uO3mD>>8~pMgx|~AU>TK>)an~&lC3w%jaHW-!pzX>6vim znQN<4Q>&|1i}Z=WmqfC{k6bIypmSO^ek=R}I`Z@7TN}qwzn}>>>onE2l%mY%b~ROo z%++M&&);r)WWxrZw6t*xrcHH}PMtF=+Go|Ax^%s7Y(ivQl%b}2depkAnRm?VO>q^L z0ddBA_x0b4ec=h%k7NdyCS%hdi>Ov~$iP9<1VV`mr{wzt&9We4U~p(iq~4IwmTXHc zT#*(<&YvHyOGP9;$^1Dd< zlNM%(54%Wt*_vD0r$%;!mDinFW3`HieL@|3f%h~z3w1Dk%kdU*SqhlU>g0+-*)_)c zgz0)c9}zZYld&ZR((&On=G))A<+^k04xjHw?zaR47;-O)3^PYdI4H<3LYrS&U)9=f zNeUu?L1s&S-Ud2LZ<&&;b$@Z?w#p#?*;*1CqqA-GjnpT_CrJMO0rUKHx+1^4T#{zn z!SJi@ze7C2jBf$a%$x>R(IvPTe!vJQoD4rD4pu{|34JN?dHJWe;z;hEW5@hyQlh_q zRPDo3j4i<&=pW=89bMNlar}aH=^25+{sF-uMpxaqt=M3eJul1Xo)=4!wgv@t8Ax25 z!F7F*-jHN2i4P7AT&yt|s+#o1z`&A|T z^tBma(JG5#`baKZ!0wu4T!zjqsZ!#1Sjf(ud3E5QJFmHBM$@9AV)>tGO9~5!$y8L@ zKE0u=oV-%gJ-udvjNLgg0ixJmm6K~v-9K%^hG}O+=jKj*GGG>oi7m^KB=aLPva-o% z6=m}p7s!`kbD}5)Zv$V6LQDTc;^Sfvth*d`OZ`c49b_=ZaGs$u1Y3=edF4;O|mmelKlL_Zi;y6idqY@Z@4dp7AtK`5d(^6RRBNq$t+v{_sqn@T+bZvjq4P)aAkW zEBnRVl*e2wT`1vLZ0CuUbLS|0R<(0+4y?9T5K2H}PluvBzPFvWr z23S5z9}>4jQ5>bzTzyGLA;f86F?(CR=4BCD{k(tlCk6O>_Vc~GqZ|H~Ck8Y==`p>s zBjzyZ1mv<9=-f&2Tuh*)RSMcoOd&)}p$JT+Lp1|YY26JP!C@nv*MF2MCjI~it#(5O zK7ZKTFLzrmgZj-){te@)l(M9noUFoXeVobEv+h>@vLy#AYplhK#>-VzH>pH&)wRuG0k=GkmJNvc)7gJea5F z0PQ~_(_p@65_Hqgz>Tf@{{?R78vt>{)3;g5af}0Rh|gQLl>9{I_AU8jW>HdoR=TxD z2N?7&-^*XN^p2`pYw?otGPR5v0u1<~^1l&cLqe=kr_`I`bZSCZh0#fkWx7_m7x}QO z-3BeiF)&-4iR_W>Fyyh9xdxM82Io?(ZAVOQTR|KgW)|SAQB#bylGWcdY+-xR1z2# zmYOxVy6M-Ku6TX)*z)Y+8n1gWKQLSdr}mH^l3BDY%IF*4G)_3=0sQ^~9AvXTHqazp zZBhp($Zr4NFp+9udS+miuP`G!pkyDwx4JUjqHn|Sh0nnE)#>HgMNaq#m+`3&$yNv> z9Pi8uq$*MgG!QxLfMWn08DT%b&L+}&QwUwcs^?C*J@EbmRVBoEo6Y`HX>(AK$s8D> z8kENCF}{6M|0F+f>K+dEuN)`o#CP`k&<_CXyQ-9ArrxB{M;tI2wefl{`x$mFk=~OG zd#!}Mj=%q4RS6j;iOZFzxl3aU8T!GpH|H22-l?3Yv;_hSbbOqw7P*J5M#%RhBO8EX~xn zJWhDGda5gYiZVbmw78!yt2PKl(okKBOySeVPD&VTOE8Jc1|9lYziIe zInz*EQMxQq9;Qw=P%zi*9bkUq_@s)8tsgPObcQqO+Eu-^yE=x&g z5S0B)p**y%Y*q8l(h76ZVyqX>!8+^dbsRE(leK2c{KfeKST}!uSA(%)dR_FT0Xn*) z4ww^SP4Uy4l&*GeLIldx7F8yxFl2{8D>3%j(t!5&l3d_Ia5oTmK)5~w^XUaX@QfPh z9D2H<(_(=1Pdh(O^XEzk29c6_8+w}R&1W*)O?_Y)#Jwuwq&8Db3No`Dgn334MiyG3RV&i; zQZFy+JvVoOYWaqJteyLgWn0Kotn+i=675e7Cs5ey zL&WK~Aq4;X@yA>*VE&4Pxc*;c2Hg{x0Pr{ngM&^W6YKv39l4RzNaBOl5oByoqBP3; zufR%XBU8w*xiIP^n^4JIUB!p~iCmBuV}Jj185v6glB3o4H8+{{)zr~-Xpx8K&>F(C z$%60S!eeUwAqJCsXKYssuf3M1Yk)&yk|Q_MMI&AMRV4evW;~jWf@abKx!P zQ)}mB<`gwoORwQ1w?E-Tm?y_o>)7SnN!$OHSy9$fUrLBfx*^B;sJTKUKV%td=!!)|HhIe z_mz#*(`c)*P@^(@ehuD?EO}V86JF%fICTcj$p>fEA;C}63N)iLguPCPr&S>i7;J3B z<9p5Z6rojAU5s(w+NG9)-hsEO@`IZOwigvdMHktct2#4A3yFZ{{Cp2%dGnI09Fw_o zerh_in;jKtF-bzA!)uF1-@x)(34ZcLr(XoO@@9Jp%)bnhjEpuWsEyHg#T)eTW_vT@ z@x14H3ow9cNyg>4FMSyo+Ixu?KWci&;yz_qQIrAEf&Vp zb?2jx)a;6is>mxV+l8JqlPM`fe=k(}w0KHQOR^!e=H(>^bY0S+2YNfq_{09Rd^!rF z%@VG1Fw;@i=|lBZM>bMl$9}tLD|-JZ^(;}5QBt_%$+i-|Ztqmf<_B4%73*RAX4Hq| z8<35_@g82>;ak#Qnc8cM!^?`!NLm%jgjQgqULU;&Vg$Y3t|Cz8={sS+4b*?QTrTWr ziXS*my+{hBnmk1|1A+l%QDb^Z;bOeKOeVtsRI4=zMj0TN!l}o>wqJD2sKlgk_|O7C zgjQHg6}f(_ez>4`Zq2E;7vENrKS05oJr_GkNkm0;-BM#XV}QsiKi<{Rpbc#WeDHeD z)9VG|8y<+BpZ2>onX}Nm+4}DTf;#nHW1j6)r~eazdAHGg{g|52pUPYcI1X3+f-om%rB1d#Efu zD`okiu%QRTv--1&@|$u4{e6enYM2fH_)SbbMqVIwa9#?!|D8sJzF|Fkw;NQy<5WqR zK0ZztYY)Vm>+`!)(%ZiXgw2cPvZBlb8}B$X3A+;oMm@Z=O9~|F9C@z&8~~HN`k-q0 zfv{`646eJc;p8c#TwaPo*+Vu6;Xj`$SG%nA+CL+R;8x!O42{jdK zZ77uca@W_jiUd5KR7%gRB3-&6k8}$$1>8?Z9Q3(~2!Sh@euN_qY&0El9cX^=MdaNs zo?epCMC3ohCp{?%TkGcT-zbiXXEKK2L59GbG4GqW#C#H4KZJa?PCLbT_Ncqq=fU zb(+xJAPetSs?w69B11w`T9eb=K0CTMPKU-IQMa8>9dBTCm-V!q3iRO+MPXbn#+h)5 z0=@kb$%dRv7T^mxM9d_3Y#yiYM{mKLjSRA>ens<5<-$jBf`=5R)-A#UC`kH2Bl&k>4!d`iQ8 zeE|>4)YIf1;zhVo7X}&_G7!@`n9hySdYy2bkI@kegJ;HawYW?!e)>#*Uw6})g#Cpz zscC%?o#{PgvFd&yKPW1)GHBA0kAlz6pX}^7a&JeyqB$s|aKn%#pG7@&ZopbN-x3>L z^x(I`np&VG>CzARtrL(}4}lv-;8Nqe3;+cwRY~E|bvi*If?6|p5;*G#)Kt-oOhAHy zrKI^J&cP?h>JlGck)S21FWD#%p*iT}Nn)fpF+Vxg*idUSedOUGo}=Xr`Jg<1|L_r{ zF~M}2`mE?^Pvq5HT6(BCdw-76(oz@(kKaZ_Tv;_bce@w$HLK5UAgIZ!#lsVB+Rw?gnrd|hh z^23WGmEjRr)(*|tjnP?u%S?B-1^LFv@~`4c90>}JjFiAwuc*l2AnJBFy=&NS<@$g!f?py5 zm;2YyCnh8Do#e;}STV?c`!!ukDCDzq%tXTA$E2=U7`yamm(q@2RKdqGsv=@-;Gn_GfUlWtO3LhDLt|3p`t& zq}IjmG??QI@zkC_1B?ui>N6C%ESWY(Rl@xXK$0#P#uv$0y4M0mdO2Ugn4!qD(wKIj zA!&_^M1KZ1@vDnO5*ZmH0q-+XBGLKy2!wDjK0+bKhZ_GgG>GTJ;e){u5%5(K;ouEF z_%t{8FQ5eWOb2%K4D@v|K?)q6iIB;Xt9VARNVG=8`Wy@?WP#`?0q#IzS;t;0)V@7` zMC?H7$M1sNhN@fqg{C^AO-q(7G@6=5P-9<-qEBug>&_Dl1QDO{;yMz%?ZbT;L>Y|8 zjVUNj%#diqBT`y=t+8<=dP27UWyPCcN1wXa|C|d%q_GfRy$Sv;j!k)xL)F9We;21a z!g3f~025$AuJCmlRxnM@WG)S@qxOP3c|@+{eums&G^%znVq(mOuuar=Uj+O{QJ;?5 z+=i=K14M>;1i*8-(Tj{G)ZZsB>8Y_Zdo29HU}9%ZN`N`9s9YNqUzlg&vA2Czu=dLyPVr9VB48}U!czq($0S-_kRG( z`s+?egEhGim+K=E`f%+7HwUfVc^YsgShn$L&|j&YH-&QmWcdUnq1_?(3AI(85<0bL z$3f(w%r z<4ZQ-vY$M7z_q(UpH)PVp1QcwR->Vn=qmSO5-|xeVlndi4Ds2N{qet_Wu22`MTUC| z1hoP|{ffHUmG#fZ<}{_m3f)sfBw?!jR1&4}0z}}P!7m2bWBi6rK6pk3@U3%atnqBP z>ClV?Ppw=BzQa?RMi&$>G`=~|aHuPk=K^R%n6IIMP6P)g>ST$o-A-(*8OWq<09-zC z;Ei&KE~B4_h)%>f-DlFt&GLd7Z0c6kX{CN?xdEQ))pZ!D>IV$=OE!a+%*oc#PFv$rkkx7@#^xpUg2K6i( zrIOU^Ya#63e_>JH8pdq`}(Tq z*6h-bwT@`Jba7!3rCBmbx)(G(5-zDzcacvLpF&&=UOF9JVt+>(2ciP00kDi(d^$8( zSbV_57>v+RM&Pa}2Y@(sNOh?!U(fUThK-^v?(Cqh`R-^4aV!=?c&RKs*;dhOP6{OE zcefu%QK>q@CGu!RsZqasUO~QF$?C?!#x0(j?1nI*RJy1%JkBQ7b>?Qq=bBP7TFo5l zBnl5NO^L~z2MsHrqlzG|c!I=|Z^VS=8X`+e3lBL;s z6;xqMeV{n17T_EZxI`d#^#WW3ob87l6gy$F(@k*+fc*NOX@J4yGYA zQn?nQ9e$!du4n584r|ogYkmgw``(|vJGU@eot;`Q3@TK*b=Rdr1-90UqqC+PgMoWqCZoMtj%QZDy>m3(XF#Z#Nc)NF!7krb-kc z3ytbePBjr7FGc?c%yQjTz@dAK~fxWU^c1BVrrO3Bg=jN(wsOlW1mjdxy#r;zOi` zpHt6@QN@Lvmh|{Fp)JiCuVh0^Vg>Rrn?a`UpV|p;*)>289CC6X({u&{!fx2o$zL>w zIp6^x0cP5KD)=nmL&(tz?|1R-;xnvwGQ7LoUHei}@-6dqi*tGu6>+JN>AuYNyc~85 z7d`2dtWYuMGd{Jwb*p8yL{_NRF_V)zdehTUwI)j%8(5#&;A8HN&k&Yc%R}P5%MK4D zvLm9>=X>L#HsR7XwV&KeGC}u1wCglH~6tu*3~jR`gnv=SfnsKCQ3bQp7gKmlvyJ z-0A@K<51_!CK{n8qL|U=q*er#LeWg#5L;`9j#PmbcRlW6GB-vC`*yj0%lw{2Y}k-7 z(8e&tR@G{?Z4FWWen=A6p-u~ra7B0Q;?#LR9&0Oajw}sV$mN-Oktp2Tg8};vKuv8U z=?dM7P~mXe5!e^^0$i7zpo27Ua*Pi9G2oJ!(PNGl4YBfW&2yzLvG2b(Z{70T^p^Id zr0x)@NfRO`W6g;Qd3m8)?N0bS%5Z12aVaWse$#M#gHa0whEN`#@C%F=bA;G#+%a{Q z93ffY^#lIX6*Un#(<64h5YXp{tmJD)A`ZUY)Gyn4a&dm{ym@cs8iI2357alPA`06Z zD$=`17B8tWnP0`BK3YknCbuuk%VYL<#l}{|%M>sfq7o9}#) z78yX+rhDUDS@0KUJ}dVdE;%4>#Cxhl|=;uy{8A0lQbHOHOVZQAmj9n zWAm(|uMtZ^C9=9;9{-E;SKvH4ypb$ayr@T#p4z*ixG*9-KM5`rsH4kb7vw}lP*vkn11 z{AFF{P%p2fz67)(qklZbEEF+68@xP-auO%;*%g5AJ_a>mm^WoKpqYB`%<)~%H#mGM zSc3+sRcwM4#bSkwJy2_1YRT>GeWA2kIa0mNS`?#cSTI^R{^=kxuv=LKcxkqk%9JGA zmi&BHpSL!yHcO$(iW$UvF9l!6O0?0E12l1_cd^cMq_-R;Dn~yf?K{x<+@-a-2(4vr zCp+B|wdSOrw=FG9G11qO`sw6 zuFVVci{^AZqCZqtuZb?s%1W+A1Y-`0D8FMzg-%~zUr)U^uc&0}p{iofW~MP|S*Jl8 zCNHDj-S)38-8klB(xn3Ucq8!*bf%hN(#*8i2WJ6H3j}v|dD6)*M?ghqNZ|j#?`t{@ z`+c)BwG}RX9S1m`)Q=vhR_f*H>cKxyQ(I890!~O1liHlmO-`N2D^*5?=~Co!TbS0O z3F*nmMeRbqG)0pbFX8bvZtrcE>hoP2T@gG?olstEy>)e2r9zpW5U$ErnfuI%jL$cu z1&L#0tBVhIcLwKMPm6sx(b+zj0TSd6umyqdV#4 z_X>9vd7ytPn9RV$1M2WLvyrH(-Ypg@lcfRSp)zjfU~y(vbZdBIm_{R0u)5e{QB<5x zlmC-88mS;a=IpPRXw;#h{{AXuAWJ6KMf;1@(z2>W%Rv6Cyota-zM{cP6P@1x0~8rT zt(QO;)~C}q$Jd4{Bck(b6d@{k4caAV`bk0vZ{OCpynyWT_ycz>X$- zVdAeCQ~DpkIbcnD0zyYA39B;r3Am6!Qz3gt;}h(V%e>Gz0>35ikW?Q85+MPyIm(eu zekZD6kjVhEH9{30(X!+uao6yD`MHe7xTORqA=Q#Nih?(8C+iuC5}q~&G3s6O)nc~W zK$x;ECMa!;(V`Dm$|97$zJgxQa8*rxmLfjG5QUi)LH+kJ@+|2Ebp*N_8H0|cMS@(R z$Bu>^6qp);d1blY(bMtkP(wpnR+ci1AW=$&Ng=LOWahTskJq$;4EK?NR)D~sey zINn}4BNeT&_HWtjjRqZKkT550hfn#5#J9C37d6$|FC?}!9D8D5Bp=r7fHT-dJOdd% z$GrtQTY7C(y%xLtWSN}n!-+BJ3tHyp+XvYE!x_mF*@Y+IbAjUE#+;1Q^s1$8_VnB( zW3OM{xTh9qvj+TeDbWt=!hIxsQa~LTj^N5#gU+ws+F#Y(H!!gkNeubc(UguX%7`|l zs4-1br=B2Nfu?Nui}MpIIge~zxORNyi)HAApNMBCs_ZYIZ}T#$)*!fb1fO{pWGjNG z!Zm%|ZKlNmq9abh55eluss0xenf{%vFI$e+w~MG{z379nvH17Lh7-#oYFh3)Y=<>g zL+myOYC>SeXRjg0FM@EW+tqHO_O zA;v%d0QE7Oc!|1*=3af6y$;>o+Dg^5wK?{f1RpC6?rQ{rZUnmG7~NU@!Oxtj=rD)G z=ue^#R#0!S(FkE9A3DQA0Re__$r5P5M;WyxqlH6fYu2x?p&m>O3Tkq7^+9A~er77b z6bn4P8EU#>yuSR8)@Q~d{V)YoJRV^Z;yx+AfxA+(54~!V6hU`VVM6lBt5qEA@&80vB0?BG}YF1*nQm z9hJI~Ls8UzPgHhL(kQv#{V04rplVe8q?NEyb{LL}@%^!Tpmvx5Ym4cw;G3^+gngV{ z@92SRdnw(i_XViho!)CWlG^V~3ior9)H$2TVy z@G?2pW%Rn_D8sDQ#+suU=2(L^-d^I|=ST16&d177dEjqJU1UOBrXpL<)8;5FT58g{ z>x-Lr6AYSBVXlXQOFlmGcoGr*e)y7E0Tjph*?3C`jX=C$>wZ3WK9Lm)y{8#TeE;H|jE zEwjH5n~gmBsB3NWm+4o^QxUxz86`-|3M>c`rsaxD?|Aabr=Dv5VQi76DP*Gl0^S+Z zs&DEg@=oFd*qejp+7bVfa?m?q2wj;ZebgUsn-?>&$$-4?JyBQR`}dR7`))hNW0q_p zJ_w6%Ne#0<7_o5q*G=E7nA8(dQTmodNaEnW$9Cv{`3D(8Yl~Q%% zUZh5Aj?+c$h0-iEp1rz$Va((PJ@UHu!P@$s$4^q1@y?TQ(^R6)m1mH$xLjqSL6y*) zqOzX=D86X?X2k+65gB1bY|3D{Lg?yZ}D$L=30(0#X2 zGByz}=Wx6-n~zE>4=%iGQQ>Q7P8-}$JF;jA#s|--e2jd@$ydSGY(qixU{jTu7CGhf6;uo5BzzZpaBfbE4is_6#q20_C zawK||3I&byywn4)CVA(t>l;g(Yi5dP+80JIW)H`q&B^CBk(fP?g-H$#-XN!j5f9*{?2*+tu`RmNFx@h#@ z{|N{ZhH7(^spBEB*@{e{JK-_}cLTbKOHiwVJJrCMpf?H3oAmYO&znfaoP(Ms<9-Vp@fPPlGOv9gIOO$R}s!&|?RDy<5C0YvjTQS`K%#Cse45ir0ckfOE_SUHK77z}mrdFY6zz@3OKbxv=5 zF~E=_$wfih%+Nv%!yVwOBoe7Wb6RIQs19j6ht6a<_!5n`85Hf(`(NuB4Cz-54t{W| zLm$zpBtk+u#uRg@)WSt8J`#MhaZ@8v5Yn_nwg}d(g}TNH;^W`1JDcB8XMak}9Mx;~ z)$3IA)mzq4KV2G3*UX6{o(P?{Nwv}beEp%jk&lRa@5GTIxL!GTyK*0{C6rnQt^jnZxZxKP+FNDbz>CC#y`#`1R|5)Ay%G&QVau=A_2X zi6u3Grfg}R-LrPrZab$UG9)cc!lJ%7bXO zh#;8Pu>`5JuV)i)QxAGritn!3m6PE??P3$JwzhuQa$;aCy*#?9c0Vuy!=(gzu^h6D zJ31l4aGZdnx{qHYXI;l@aC0D57ay}TAwDv}VBben+E3a@6ZN2{rReUO9l4nv)J~9S zb#aI_C_Y5HwlC`AAv1Wc~2BCxLH*LrjWr6bFAugME@s{O9TuY+`k1C&lmT!g#w_ z!FJXVPda9@OqX7+88~dcU76OO4v#F&+0jyCvfJ52L%5fxSN8nsQmeK6z?4fs!|j#% zg;jZd+gtnu@<(^%RFzcb?&|?K;$U9HY3M}3{=SQm4&cDtfJ=!3dpz?Hnm}nLj6Z&-ET?95m`+hyDbk@3``H(ml|e6H-h_ z($Zy>L6Ou;)am?ZHs4LfFTZo=1u?ap`qa7KqtL^F-wIaIQRA#WRY#I>rl9D^KivQW z@gJAq;K(TG-cT+#2wHq1V8ThihE0Z{P0ivYHkW1oWTX~UFx7;4ExDk ztQ={Vg$4;y=8zf#w}p!mzI`R*XX-mw_mrs84HuYqov%(vlx2~`XViQqIQ~FYVuC!2B>fys|M+HbaUAtx*}A4pb=y?FLJuDDb3n12fI`0m zhPRzPegM{=_4-Kv7%u3@y4J`UJl*euJF>X;U%r1a^Cn;t&pA3XaIa=?1n$oS1%ul? zfoz*vZwfJh2zTb6BCCl9uqWlr%uPqPD$)ZUuX2@dzU8}$f2~;^X1|{eKPM8Xe!2Hp zZoPTa#!Kyh^BBZFpU`&1<98em@fis3we_;h36Y5rdlHSY$uR*pu1)Iv^qIjqZb5N{ zL4}dmZv*;aF5KICm+mFT={C6NIx7#2dFQ&FobBQ>*=eYbd-~FxjeFe00ypM$SUn4! zRJfmtD-?3M#NS*PfgyCqE3mf2lP|u8ugMRS)lY<5EP-|!7an;I-Xw<>q?x`gA!0$| zG?YcEkXR}T2$cr=tL7nnK2;*|VvD~hC(@(1JU%yK5p|{;T?tiEkJr%rt8rn&TCdam zI}=Md+!ZEx5;#9Ay0$iPSr@O>hu?v1L_F1T(~VAs#O5fHA)Lxmm2x9tQy;u(54s|A zY)S(Bc+bnUzQBF6f1)jJ2FHESm=*~gJPvl%I7sLqCzX6tu;x4Z(j3f+`+Tt00IF8r z1VrGk6o|3*Lp>YY1?;8Z_*Wq1!EsM#4IzSeSWx^@8#tEe(P30W-Ay1#NI_#rkDt3c zbvK*PxA&#ATkOTCH)cTJOLQDK&_bo=221+f*q+mx zFxg@U<87ooFexphh!K*MC9Ukm@TEE63!vfKelY3pnIS||&u@72z0%J113mVBHgU-K`db;*h5tE_TTrw9UaW^kL60>N zAJXRq{t%A=oyZE18R%+Y(f|E33PHgV>T~KXKl`te?=@WysHijrIkv8=QJabf{D@n3hPxzVZdF8=m=8TiLy% zx;w<5xQ!&4ZB5JDH5qXYMNNtZ%P+6iU9ZR^ikK!UgcXRXI zt+&H_&1@d&VRM5C7SVHVZ-qU!P2EYB5lwV`0_S!O)0G<*k3~Xyp-5s2K`k;b56{Bo z);8nMEw-;1U;c5Vxy^B*ywfzU%*h`XRl(Hqmo)*ekipM3K>8{mr$rDcI}DE|Q0#CNWqXRku{w~741shoDa-{UYpl}!X-zgq$Hg~`aXbCI`GRyI0Enb_z% zIiKH0MYD5nsa;>oH{_wjKzuUhtQ@w0R1YwVj~vO{a@ zzk~wG#mVucy10gxJ#<}$aJdBQ`4V4)mIG+8XRL?e)jnXfdEj$TebiVx-quAi$GNip z?l+P5lnZ5FvMP==QiH_5Zmd*B9lnL%i-py}i%-0>m1N;grzq>tQ3 zHHOE`YhMxSk4DjxZPe5 zPvv~PbZJpR;?UsPiwN$3!ROaQeBMi3pyMwbs6Y}5S0F)W!3v}tZXS}p!$bD+_j>z9 z6mJZEQovVHUmv)mt*mg(x^F3jQy2hyC7i)lh!1&mEfZJZaOeYTz#qu%0(U_Ggcu`i z^v>|^-H$vnY^T^r^3_+!111{Wc3UHLwdvPin-Bx9mG2Tj?k5h=5y!Mg2QYyaa113^ zkyaR>z^z!-)|F>J#YVzWwCL(-Hhdy5FnGt9xoWt{{tg!hKmndEX}ZFk;b z>jZ}o-zz>$7C|o@=t{bU25&&~7DxlI7l;H(Dc+Qq>wR8VQC_Bw2<{e$(1oYCg~heG zu}zJ))fAg7rIrzXma*o6{y}^O$Yl%MlddLy!+AI!tc6zsPm}99J-sD^U&5OGa3-D5HwHc{Og6ig z6qs2aWPu7`{l*KcXaSiQJXOBn)lat5>-v?Y$~?VaAUx;Ark;Vng7y4FTbeO_7p zl7g01s-`TlNOAAty7Y!>>wFsil~Zq%ZN$qo2RVyBFt}RAg@&5<3AIY(H$t)J!fdmb z1ww_rme^n)B@TfPK!0}m)E8tY{U(tMo*pWN;0V6rb`0)v;v@Sh$MY!igN+8h4F$G!GoL{80THYvkrSTLXKq}_#6Rn zMpDN8<81oq$;CyA>kkH?dj8g zUdi<(yx5+R5l!dB&u4SISk!+!+o=-bvk-CM0~{a4yOtWctUJB<9?K&bhj#GU=YU#< zel*MzSXI!NfE)qO(RxiA3ucpVsK-RdflrhCKrL8*$g_ln{)wR0P-zN zoV3A#BHkD7rp^8v`lhXWv5-H%t!H@1hJKs%7uTcwjF7I9Q&UJrtp3&QI zMjtxP2%nCl6<|h)XnbXjWE7Qd@Aa9PVhX>Wk6YIYxKJ3~mE1cN9 z!Nz9$X&)^$Z*iPW9^5nD2svNyEoj;$(Kki0a|b4MUwMI_ufM;N7_;ByCovZ^)@H5! zRUr~}f3&}1!14K~z&DW-c5DS<$gIH#S|4&}%%?`4JUKf0Bn)PMhf4GhLw8-hfMV4u z`ZI_*^$2m9rWwq_1sXvb#z8USa&uXFU1_}T(5ALq?mUAsy9#VpUF@NMm@gc9z_DKw z%+Xm#yoa-g&~ie*q|ro1Qg@C}$B6g3>=j^=;TLjg0RMgi)U9xKPpZes9_(mL|E5C{ z?7}vnh7VOj;ppQdPmBZvsNP0RS6|7v*UwLd8mSX7kU7K#B1n)?q0T z14e2K1fl4%<9j4EYAl(kD6flffw|hm0vR8E$?Pj(wsr@RYkvq0q|`L$^9Z}0q z*b9i0^m#Qxt!f?g31C|xg$`^u8%MDUuM6S~`a#x+Li5H_(RwO36QyhTT$aFKYHR$P zjG7Fxeo3_6CEVP(B|sE5QdX5#IB;v#0Ssr|)IWf~zrs2k&)$KMYDTrq3w-_X;H{6K z7ZlpgmKK{Rh*O6=(XAoWzA2a3%!0@*&#Lv%e2-_EKMAqkuP&ZIPZ{@G97RNpY`wki zeEGJyZMPgqLQviqw?j;!%K=|!uAxNO3q!eB*W-_$MhZ#1Kq%} z$9p&e?%(YLXFh8uG%}2GsIwxGt~NG3JHF;fZ$@@aY0vuF_R69S)J|gAqD38xqj)^A zys}s+*-%guu1wCF7*0)K4**W}Q?G(t(S1)mdT6V|b;K3OV ze-?_iQq9n3dx!}51b%gW^%ZqQ)-4IiKut7_nn8bVg7c9B!NBpv+HKmea<(_o8r^9F zwFY#VTE`}1sHd1dALX;W#M1PxLC&+$bq%{SmzEYr{Z|<5KOz(z^}&UNxz>VBjYIYE z>)%=5){$3lE-C1?)D3uBi}M_`tA!l>7Km*i;|}vXojEVK$x1F!q8c9`gLiC2$EGUk z`^AKBZ!II?@!L*z*Ihx!X}EhKR$oLiu>3lG8?3_!<_vc^yr?ZfcTIGZZTr!tZGW2j z7Ht#hI`XQ!13m3K{mZOLO(?bIzzgw<_WCX@NTRNMA=MP>MlZyr3Qe`i1?Eo7LzPo6 zlB zF~^h9uSy?|tG2Jlppl|^|f>RmQU zawoXQ?myfQC&|uRwq3S6~Nmf2bDRJqV_-EI1m{!SaCWB@#xE=+!Ud z#X;1(vbm~|k?>gJEBj(U)DsYxu(qhZtNb=3r2c)RXE7j((AlXM2{rV11FA67o{P?o z=}7t2ck%yosRG@y816AZR6ffO#%>2)7Q46Xt5vt&m;K1H&g5Q8-<@OCUHi8iYM0%! zXioj2fvqRQF78>op=9B@)AU;FrhX(F!KQ+aWIE&*-k);PzGn}oK=q7t+8rDkR!!uaa>r#dWnP#!jAe? zN?B5UiXd&mRWOlG8ka=C>#2A)okA8W07uWD z|HE^%+r;>`xETEeucP|-1O}(3i#R^T!IBE82Rh{8QN{_OE*5#j3V8pU)Jh+&82##4 zi(&=(_BL~SyY$b8Z2Dw!QDjZ@I zU%x7OO&gM|O`ZW$5m>cJje_iRSSYyOGsEl@sNz@c2lt^^>L2?KUIWgYfzqEUD%v{> zCM?exH=*AR@(di|DDfH%Nl1hx+~I7flu5`rO;d0kC1=Y!rBIl)rf8U%P{$L*!qCFJ zNTL7z?q}-J${UDzqZ-)!rHaX7k#250t7nUfFJQ6Rt^&WP-$bQ;A6?RxUniRq=p{?| zb4;Mq5dU_@BecD9W@Kt*r#4;i@2b#j5aV)yP!ZYR& zt|;lXe|!pw55Zjst})vyuQ1zVi(6vadjGE27`F#lXY2n22NTCzLT&a%1iyw)GPBXB z$8G&%wN06IRZEMo3A+K0n`y`!`3V9Cnz_MU{$q%5X`n8%kg5TN{%-)$2z3W)D*68V z5`YDI_E80S8s3fsdx|XqdHU)Kcl3zOM*ly!|9cv8z&Oi|O*P9Ea&6$!L4F$G5fBBz z5<~sCcr;z?!KQlI6I5SHf#)VW##yWC-0e>6kH#7n+d| z9PsM#tXVLz3IRIk<>|j|RiKB*L_lEL3ujwUS8bdkWq=yIcrVev6v$n`a;&hL>!)3`eD@DG(QGdTPHB`=G z``P?G7+fFuh7FuM<_vW$ZSEVoXBCY&g4t_($VUNhoGS%Y0Kz1P1|9I<>3hpS0LRH{ z1;qQ?R(%;leSdfl^_%3<^3`dR=Ani5FWiaX$>)ez*Qvcl;fu5beY(-;l9iWFbS`?$ zwP)qZ9-5z`;hm;);H%*NA;>u>;2iN;(&u^Z>ZChacJ*6#;um|2dvZ??vA?H>y3o^u z&mQV^Cjd758Crh=@!>lmM!T-Ac1Ht~sDs)QZL<;IQy<&l=S@?4$@{=3#@#p$o&=5* z!dz5BJV_mNC+AWF$4=jmx+yJj#b&!pT{MwTpS>Aq@HgmZruhhhU;*~gaR{TLt7gR4 z6IaO0j3d%Zfy+gh^lfG1dIWW*oHU5pwhA!AT`KV*dEoCYd%Z=B|b0xSw$|Xw$(hw5@&l(VN zF#~@0fFJ@CG{?@CIGqf8p|CH@H36P5!uAgTCg9pxF=G1q^se13K3o&+=Ebo;76v0r zxbwXc=RCF9ogAcAA`5jI6<9|Gd)e-yKCzc;bY3op&ksX_uQ{l|W*fpnN&>Gw2J&tP zzg2+ygW$b$xFCc_^MWq`;SOmu$fPW$l$4T|zN?GfUkW(ve$MYHcj7@Sxzv;Jp-CeBU zatCdi!H9_6vd}PPT&F6rQyr*ObZRx7u`!JBFi1XNe^~>4Gbf-I7}wqC-Y^HoTphS} z0#Yx9sn_@qCVdbhbNa5Xav!l04C@4CMaS_U4mH4$fqd++TJ1eMcTR2C@WUjB16gF; zmwukS7kasv(Cr3!WC(!)Sss?k8#$KT{otnKTYRqWWfKgR_r%gyLYA~Nn5m;|Qhj1; z&XUSO>V4RvjT3198Z~ZfpPSN9-G@0B^X*CU6s=RSmJ_(*xmu8NptI^q71Xe1k`Z)5 z0=udSFd&3XM8#K&(Kh4_+*?+R>e-#++s2QM&;Rx)XA=iq{-qe9;$NsD4(dV0=-r+t zo_G^}^}NF2z@A#b>omOYA;Qu3G`)-lSQqNukq8I{l@Ykn!6iO@`RZ!+YN1g6{YSf2 z|8R~&)Z9HmJu3%X#vo@GZVTu9%|<AYBYlyEzT? z#{DQbq6LD(OBS>u#~rRJ93R4Oa&LfX+!SCR57Lk8gWz>P!PT|yh&RzbL2Q5ji!a`% zjPHK+)w>6Q5I@`9VPp#YB0v{EO0P}g*_N~hpN%dY>@`cJ1v>(-_Q1@b>EQy6Uod zwOs>8=FLyZn7e3&y&rg15ASUp2mG)dQA6Adw*wVgmJHjiY0D4x3M@6~TE`V{r<7to zlL|;p`I7RhpD*cHLP#g`O2z4^@YoUcrrO&(xX}C{`wL&MFKhpZS@P_tmU z**}6uv(k-t%in8UcJ4)JE*G_@KFbyk^B40*s0+coam{1TQJdMq#`IHc;h<bH=`bdS1ytMm0iSv6Z_-wn=5O%a=7 zRb<>kO3o%GBwQbcYuF{Be8@0ilmckCyt-kmV`sZkx}-dBX#B|Y9j>I8&5I=ENEx<~>zUSvojWCG$o zf>)wU57X8tgLoBu<#0$1qm(mYU4PY`Tt@x8siePdSUj2{a(CmrxQ1zp4qJns6nX`& zs9F%;v9NG3R9dol{*uLKIQGwoRBMOL)?uY0TUoAtT>Gtli} zrz~o&?F#HeM`Jh(VH7Y`cw^X959jAhbb}&-l$3x>*N*F{HRIYbw7qv<@Jo|(Gi^SX zsXaUiZ-Ec&tNbq`uVwS`8bAfo#y{Gvof5pz;(t$XjwS48w=bh@h5Z?=Ef z9v=^1QsYXnzlqSPsl9}jh7ITbwS;!jqIPlxrWfI&0D4`3|L5tr-3XdOOAn-UWg_@d z#irhlguX=xl6`NiUQ1m<-%P??PII-jxF1B%S8@)eel&|+w9Mx+v4O;Kl%1&zC3{CU4Zit4JUZG zJn&>ha6uL{wCv}l<4ey{uZN(5$w}fQ_07F!7r`g5P6FMnugX?_&+xi)1f7c423LF;^?^GIqwaG@^%J;<5y`1{ z(c+;u-mtB*t+e&^!IxE7;FvS;%OLlrg&04>xqc{s3^>T}cMvq(sR6KqOCZJHXK><9 z;|4#8apO6^&*1ot)%wlEHzg(CT*LJt4(B0^@B3#sod3tzdjK?bwg2OudlNz~;}Swx zflR{Q$QB2PfQz6axJ4sG5oC`DDDGLcbyaI^t=eI0m$kOGwc5S+t{rd3+xG2l-%fM- z|D1aRg6;eJ{uK;JNX~f9Gd|Dfc~0Wao%S8}BKrpW+`0It2;VrwGLiU*V?~-;$G&Wt zkFPOSl{8jauQP9g@#WK5$0P`JhICUQQv#CPi}gpgj)wrTPsH`|`b!=n;s~fy&sOlJ zuB>)eZ3nl5o{}H0?&!F?rG>NPh+&W`)m^_NXtlv53j=HF=85N&A-BAc zFnL0^Y39gWbN3;Q;#2jvF0MnXji*7Ogx7cKmC6O=J8|W#(%$O%W#fyxGBa~0jO-~Y zo#VU><6u7h0oYD2d=<+&^s%*-dh>gfE9^NN1=C>6q)UPhApvZTt*V@S?d;Mo3?GVZJZ_<6gHh3#ToFPii*Ej5yhVlh7 zW-OXo@6?i191NfeBtjJFcRm9B{0E+GcF|KRKM}O=; zZU6e+Rk-VJd8DsW8)&@qdGU!YTbtL+Zd|^{DdkYO5xL0QpX99=9rNyRMx>SsvU0^c z<2d3kIqd>lYmY#B={o{I!}|o@MsFkO^hNHPojcuooX$6$_d4;xU&|v3 zutK_V9tpe_>;v;&;WkUA&SUHf2*apw@G@Xw{+~bR&s^$PbDz)8X|sP|4+~4k92pTG z6Df9y^XcjZNp1Gm*?(rGL?l=u#YS;H_d71&I{7S3;A*fDTzqI4T7NF~Rq)}5NVq84 zKY5&{$v=tc+_P74%;RBFq{1g`An;k{>{zA6k$7^5>1C^q z9tk0E=)f&u{vkKTmEwBGmD0^Au6=gKCc~1c=a~6%(GzMi{}J!Xd4^3h9#(TpN?mIz zQr=9dxZA~T+JEbwJDqjo8dAnpmphLgbCy>X$2W|tbKXa2dFhlXr9d$-m=S0HI>cRv z@j=UjA1B8LxY>X^03Ok|<|k0S&QanZeN}@(9pGIqL;tFxPDcWzOyD;qk$K}`Mu<~ zp4+wbo2#$sUR&AK_1My7d`xWe3Hzg)uOcMUX+i9-v2|?_>n?c4vR}f8U<+jXE;*Ap zkkV9`pDU#`xw?k0tbeY(gT7{Z|Jtodzl^=cblBJ}Xxy1vO`wHLd#Gp4syAL*-CceC z&cmx(x;DQtZd9~2*TYH4fNy^ia>zUwhED?&9g4m&WZK5i7&(m70fW2S*Ef7eeY(qJ z&bsw%=Ou5Vc+#I-_sp)n=PQVtWPPOf^Wy^oyyKP!tVuSSyaEG#+kes427Tx>I1O*a zEQS4@2#&x(?2F11EnzxQ*+366Sm^+BK%)SoaOpWc?Kc>SYHkO4rmb&Ppe7*MosWWm z(#@NCc+>T}HZN_v{};C}l7Jt^hle@;1B~Ge+d~e1_;vi#l3cR$3VGB*7zf}rJ|i$B zi@m;tpoK;_`eXa>dBqcNcx$bVefm0L`w(l-Ve zxdNJrNMa?qqvs=TPs{Y~o^#`ioQ=*-XiOGkGaP{8E0whF9&h`y&5{sP&0xR+70pji zZcn94+ml*IBGav7keKe&D@-G_(Sjcsa5S2)Ew-kNO{3Y zyqtPxpo;#+lr;yR`w;&m?w(Ugr$m8xD2~J^S(+A=?Vf#?k0jf5Y0t?M#QVWVDMeK* z`S6ob{$kt#`gYnl@=6HJzI9YmM^tmj3Z?45JKY!5q`(C{+Uzttf9K;8k`C^&I5#dH zzheAyjbCe@vlBBe`RjV%kb%OKI zKCoUR8Evp2S|8 z`+eK`&CM^$uYK>yd+ubfecVN#U-8=CU!06EN;XS}`YH*lMr&VYhgeYV!TY60C_HH+ z*;?W!2;P`E<6d@+oseqJnpHl2!Xa}GY^Jj=y$zNIb56#%&SH;4 zV0p4IHl~YVT{3ZN2uo6dxv$H@V5GQ1j_U^Ea%gC&*)t%={YXm-cvoQxrIOc|2H0o&s@1;pN#2t1 z#Jc!5-FI-)9pfC~H6e;32klFopH{lCg3grAgREcu;hUGTkxDjW&}v|;$Y;uOw>r;TNm>9;erb2Y7io(flheETXAqNiF@wwf|$oBOB&CrG58y z?^8_?7P`fo^wF1)h4^CM`cwFF&7nUGACsDhNCnSxxYs#GlVkTm%cxL&{F7-x#)XFO zKmXh{WkueC*q8;@*o7wgX^@d{jwDB*L*$bjly0B8VhW}OeN_rx_buSCFujf|qSzML zpv7YbGDHAtC6J4;d@yM~liu`YLg{~z#Nzny72!N@H5Y$Q-EJ|IdGIe})z~YGohDf}+%4ixrrTCRsor+jUBi=vyuN(M#pf5A=jT9E( z&HpJAreoDU1B3E7#B5|LGCk>~hr$K5D_mdjE6p?*uQA>0BBww~LF$SR3(u-{>3a%| zyh=rQ-kUr`ug_5i;o2mhuq=MZPyl5`UKOSg|GMjbws78=9x@C}Ge0XLymx zAcyRZ&f(H*=;*4KXu>upUmO^u5QT�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

b5C7duMgsj};*uSzl#vKCIa;4s zKD23&OR2A`uiM18uNtLs==_X~e11zaCu26Q2#@Qv(fby$9bqFtL!eLChfbM|n$`hO z2(;;i21q@^2In@7e+A>4!h1sB!?-w$;b}&7hLXivj=-+SE)p1S%B-v~78HZ=US~g8 z&AE?rxc&zMa(L#ieCKK*NUhQxy9tA5`!Lh*?Wfah_S%>Pm!&XNSIUwH$k27K2|p&A zt%PXX0@01Z2L9OA38eG|Z3_UkHJsYE>6;*xlGZ9>>}*G1e}c^ldXrhKNrcUuSg^Rb zKdvxsaavm&jY(0RcsptwICLa5(A}R+?Q+lU;YQ|?dRC?s_@Qo~DA1&jbf0h-(G07X z(+0Ira0H&v{X%K&Zho9||AiZ8ZZutEA1KT>#OeK&q1qT*L1~Gxw5Y_Fke=mz2TYgl zE>kXRFjn*C_$1QmI)!H297M|bPfl)v293R+N zw|wasmzUSXXBp50uSTB=Q_0$b*}CMsW;2;Ivu*WsGj}_qar0d zG4h%aWu(;#wt`<-hBNuANDn$-8Z|=h!+a2NKBV->j8>YHH9YJ3XL7mI@*YMPa=Fw> z7cFtIA#XaFo5k!OOB!|Wq=y8Cd;|(?{Sux(6kWFuVUz9rEmj2H${q~DFZlB+gA!eNCCz{YH*UAs`Bk@RI6Hrx-wkpkP zrUKq-OI~@&g27Ut#0D$-mu^&sAe|UJ6=)CRk~n9&CD@5)lLhA?#fMHn9!l{TI^~CU z)X#~$beR<+}UUk-Ox)!1D55 zo_UNC$!%P49QjXtqp*S_d*Dcnqf>`WoIb>ojT=|R$FGl0O^se3A5T6G41S#FAJ+x; z@VqM|h2v86?-@95AQS~@!av*EBC!R?$`xncAmz;W;Bf_LJS$Z1FNQOQU-1uZm|9|D z2DVOaTl?kXQjCT=CD&9Wcu)VOD7ZxCkODm+ zkCSW?PlT_8d&ysRKV=cVy}GLPsh59!*0sz0{8bH=`8Ae(xf7ZYax7o1vdwpItP|R` z0rm6yBXat4ZRDba;F0JBmT*JLx}1;|#*lt}R{qkQgu&Je6Ot2SQ@cWoR|YFkNI-Ky z*T{~8cjefV8n;3tP_SUpm8T~-UjS&G#j>JqqFpmUHq6OJv>q2jh@p0Bp}FxC`B8{w znP|g8nOZBi+fz3jG#*+V>mR6rY}!}vA2ty}xVM5Ct+0zFkJbB^T0aaBZf=61vua?r=7U z`LVyZud(w=@jU+m|3DDWQ;Y@AI!`D}I-v&jx;Z*3;%GWS?DMuypWrJ@&XBvBCGYwR z4@By8zUloeb3|Co5eiaEHU$Vj2a^jwdF%BrKH569jfV2KVKg+k4w)TftRB1hX3(h| zcS`MIN_z_AS?K)0*|$plCdpHW1{ZUf^x(u4rwl)V<&+`b=OR<%E9Bxy*tIuJ(m_NR zfiN-&pb2u}2XDXe5ApTy5q=~Zp3LJfznm8y=Uvqo^I}di=%ED4eKs(-P3jMx%~4taQArE@DS`odKP*Sb9Tt# z0S(=s*oSj8FD?xhBYM#2m5DyT!%OwA+=W9y!&N|VSXl6od_mPpgiDr|&cD>!mlP8z zWhsfbcS*dw&v1EHaL-`O11H=oe4wQx#I+l=dn-BQcS8+!Ql7D8#j1+o?6Uk7DY#go zk^YK6acik8qIZ0|Op0&QJC39N*(^$<)IU?2FD`lZEM^_TVIo`OnX<+7PMA@=n&2se zAhh^FC_tkqL?>zrN!59~cAIsES97>yF-q36&JHS1gG{%B57I(8WPmB9S45Ua8WfZ0%j?!WYP^k&#cl(eLb! z7CKn+O)?ZytZ?(T?dP1+G-keIWP3qbiPQ3tGm&ij;Kk?uac0x-W&!5S(Bk=nj>v@z zyW*ph+sHAsz1ObpUb1jtL)#xp%5(qN#K#fC5k$-TU#WznYG+Ig#8+Y@uoyEC`w&>`n`7i=cFhnqa| zg3dm509R&~oEKfXnJAe!3tmFQfG(KUQ&0rckW+U3x8cQ8mEqN|FgJvoG8WD^7Uk%a zse#q!%#l-=zUiD(v|5_(41;L*{UXZ9#wVF8!M1l8E!hhj zj1^^Y>OW?``IRNIr~NpcWqZtUK+r$aRcd?ELa=B2vAvU=b|RE$=A`Mw*2nk27)<pQa?+o^{A&^2sPlpyx%`+{$mz;UziF@((H$gF!6)|Snpf=uM z9$a%f-ku25#VbkUmnTnrORSsPx17_}?d)~-bj6Uj*B&`mRlVP>=;?W}xA$+|_Ha2G z7W_n9t|w)7k7NtOogshg^{!`W5J@^@VWRb%s*EUD50c4B^DiOOY8yME;#$_}w=!Z` zOr*d2GueWPdjH}($Pe_@c1`&CI zx)i8!LKX?6NM}cTAu6QE5zU5qnwv9ZoeL&v1ifVJzT}!*JKk9(K^x>$_$_f9`RZLbJifBfB`2wDGC_$uL)j_b}YEgSHJh+ zyC1!~v2IgJOUrGpN5@;v{o*RMI-Zc2&CAb~`=zCJ%Yt;~SEN7pidqqEzk!5vADq)Z zw5)4o^Cd*iZ{Lovx33SydhqHCsw+HnO=Y7LCDJ?0B!u4b%nd_rP~GTt6+C7ZzAK&> zlnJg|syKj&h`ayRTrpK0(fAybl-F(ET+~M1Jdt;o@x!M24~=(con+rz>Q~ukYAG0J zU-4HRVxN@xeafEpC4qk2N6&SK7B$y1o``lkYAhJ!Jn=i^q9xKg_ zH>qT@7Svuw_O+!u6B7LmYzHI;$`lHhLh+O||3*+r z&+{Papf|_SGE!P*oF5BZ7~vh3d0BXkM7t|l5xubBS%uFAqD78p@{t8iWzE|W!g$=7sLWq!|2pt7#`5Rc2uWiQSQU1 zk^rG`$FtG}Fj;6U`3<&wtXebgIP*|WpuRmMdC#5z?H<$G9ffSz8YNp}&)8+|Hn*}v zFrQW@2euO5>lQV)`pM@->HU43zN)aD0Ce)3x~jG^)$uQulzD_8DrIhVB7L}lv3_pe#DzNW3EQg5hRr3h`Q zY_VuAt7*jTx~^?`?Yn8Gxk76}`fu0ib8 zi$Wz{%>S0R5!Zq^u$Z}Fibs|WAKh%tINLDU(g2P4*d&N!{Idza+TQ)oFMms9KVC-QV0&t&r||A^>Vp*?4%b+}>i zxOG)&UDSvX^6)o;@8bVVJ;|yg!Ht@y+u;-E>QBaNuy@G$99ZX*#)=9giVHk)z zn9eg06|*5%oM8U_7?`4=xb){G%r9x(j=J3%% zjuWEJZ@Snr{r_=*&^w;k71WWZ$df8B5>AccH@zcRwE@(&mnetpgT75nni?^Fjhwe}lKNv(LK8sML$j6AIn`lL=$RZG#pcIMFWEDOd ze}7FI@P{+ffIkd4FpaKcb6JRvKgzpLM8{fr6y3Qef!WO;JpT8M);_oa0QuocA zm6Z`9V{hOdH3#b`UFd?%%wC|;e%iTfbi>B=pFGL)^SNEuBXzxtd(6!hu0@%NHDVLb zWuOs%kmpKbseNHTa~bxlf)_wBESAyRozu19!7K83qLN0SGxx9yFuX+BVh|JzK~Ezl z5X=odA*JUe%Al!Zrt`n-rz{@#Jm)^czI3_r*v%|?>&_!bf|chM77F(l7B(ucwfL)2 zHWr{;h14@k>xo7lsLKv&j*rp6uz1kIvR!7DH4CQZ=ESCi=H`SZW_fbqeT8@=2fz74 z&6I2N$ngC{R!>O1aOj$DJBCy5(1sgohF>8C!O(^@=SY1yC3;^6eI{uK$LtPcDFrTG zH)Va%Wcb7FW5>8X$pI8A1gR;7YE04U5G9-kpwFWlC45q`@Q&(Cm?+UP_z%dtQanSF z4I^O_ZuQo}@?&Aw+f?&NO>A&*SX^wFGc?4$ayiQAxX(HARbH8~JTHLPy^Cbb+rbND z`6b4h%BH_K!cZ8Qnh_jqfqs}G5a$Ugm-gRxZrz*YdLK^7 z;USC`St6K~j9q1r7wL4dK{{VQKVMynreX%T-@!3jQ^;C_NoR=T)0ZqsC-re!l__LR za8&Thc#C~SL`1y7zOY}fSK3yEsx^U}u|>7W8XIe}8J|t3x{CNTqVE23<{MCzPY%u5 zBd6dgCdqighhx%E_T^s5PZmM2-R_P>5uiA?m+|4xJS{s2{-f0RhyRIVcg)oz8GabY z#$+Qi?QZ5L$=)>$y9-M?4@GY6*zVlaynK1mXg+<39(R9zJ##?#?~RuZtRI}(+&2E! z{;6|!A9w?6WqjtJc|vx*41J@pF=>pIdJ+D9@Dasz!$g*hN_A$on}9T>ZX-w@W~CwfG-FU22_$pY?>{>~jN!_f0)d`hv08=0R` z!z%WLihB?jNLM|p6Hy=I^^N|x4RLGOP8M?lP->2s@fsXD$(Ux1ri&ZLciyD08scyR zXqWEd@AMZsG^9`ZHqoM14RxMOIY#Dsesi7l%v>Vv7ntc{y$A7GdFC&&eWIL2mX}IS zRP@nOBS_E!g0u={DCzSIjQF9SL%uL6FBm3Cm-~=&((ufiY2o4H`r9Y(3K=IIA4C{_ z6~5_YI!Ne$!^oPju(aPlFpxCirLgziZ3OffbLItcmI$aydOf`)zogfo!8ZC1UY7nb zsi(N{Med90$2F$}0MqRk9sr!sBv~TdLM{^eGBvOMT3`R`s~VErJK-80c1`p`cPu>f zyzB_}M7wozMM|QEgeT7?+6S|^z~jBm4rtQdQxQQPNxIoo92dzBuoqMYFv1xQWq@P6 zyB?DoZ>Siw4ONC2vU9VHDGBIjA8Kn_z{oz_)19-Zva+KzRIuR&cDk2=-f#MLv~g_J zx?9r?p>nx;wNgd``~#VW?_-PlkxcNzdLNJ-Mb9b+k6Wr)1MQs6tuS_wR+tq-$qLM# zM{rm$f@qJl9Q|sr?`Y~l6mNe8KbCTn_3j1rleR7ZFOfQvF(EtI6cuBUWvmmNER&e0 zRHZ(kXM`L6dN1khT;1wiTfb->(#VY6{QyKkRYbUBjn#@7LS~aM7Os%XETlHil-1=M zlWcRT4gXrzzv7v=?18+vAIuS5HWOrf`k9BOa}c4}O##l|(|a$I_;_0WAeSaNK-7*A zBXTqv0IV?Fy+{K{M98`m2NDJQK;oc{3M-7bZL1$Ae_Qq3gY zam>Wx_b<9?(~hR|Hr%?nmls|ZUYuZMdRM5NXf-yXMvJ#79}3UhESq4y29<{4Rpl{J zy7bULyp}zN+3tQVjHx{Q#}{(%XG!-@<3AB4kLKEEXTOM+sJUJrhWvWwI$0O!UxpZt zPZZi1bhD^CFoPK!AOATrGQF|O*i@B8CNE;L+>28qA_xg|1u;w-^AY4&3GA-}sH2pj z65l5W-Df#xpAdQk=^1%XDM%LWcyP)!_Il&Ww;eZD&l6bYlmpUKqhS)_3&;D7V;#jZ zt(IthC;Exq&+ol_YFq23zQenFY3A9tKmlT9l6YsZfNA}d*B$V#2)}~gW{%iP$|mf$EL#-0TX@1Yo}cFQPpGke zls^IV%VzRD%q$tl`NhT+w)PlX7hCmuu_8@o>tC?2bbI@N^HL!v^2~v;EWVtNtg$AH zZ+v52V{;9}?o}7(N5}a31$FEEnc!f{+6`)k(iP(qh_kyH>y8qsfA+a$iL0D*ZrL*I z$$>g5G@Mc+mF^4uxDbIkdS-8K(e;jzgvib0J_^AW6a?t%jOI3Dhs9k-n40-l=ig79#v-G+6#bpG;WH%WpreEiDA zamy#J9SDq$uom8;Sf}{?M>S^Q;kJ{`T3UB=(uRpQ#W=ZoOjdk_-0iId9j^fA5>z z{-E%!xCZJQ9>w|57&v%S@(t&#L8_sK*bq^%LS;v1z5a)I{VON$XHQ+ql6wx{PyX<_ z#X^_RLoVXjasbJco+YDAO)`J? z<$!PG+?UFtjP^D23Zpi1dQzOH(by|DFIwa-W6{z7)iUAr#JpQvF3w@lju%#yuS`kA zg(o)E3j>Q>8gk#d0;mA-+^-bRoz`6D1SFQ;TKX|ma$;X8DV9x*aQ{FEVa zF=lg8X_c`iO{-A!-R5)(Zzu}pUu7RpPf{4d^9%Yde0+R}E_116O_)oeg~d*g-9T%9 zL7lNKGf=5QRRqy+KwXi^xfYEt99Rwo$LpA~%^zNN;?l{DJ0~8$erTk-ufDl?=i27s z9f$L?dEpJ=JwWLD%C#*z_`6CiM$kz1hA--0XYQ8PJ&QZ_4hoR=iE0ih%S-(jsyLM!hG#SftGs)zmBO}5{JEPO3Pa>`3+r6u#(bKTm=$Vcu^ z)LVNjW1V&Fx6}B22I_acypt?etDUtQk%tj%_C$wZW{L*rG~HwcW+9R-b|WB;H21XM z#V$@>Bv0G|aDVi?inS!(Wc2gX zbgJbv(?Ya65}Ut7QO*!O|7`e!-Zh8P`ii;dFRmDbAOiT)s1H>-lo4j-%jd2W(AC0G zE4MwwJmjz0k&nQO+134G#EnfODFybu_H$VB$=tQtgwgV*#28}9NKLX7-#0bcwsFzg zqU@~9?c496d2t!AY-ZM&G>1Pu+=(Uq!7HLM7Ml6N>QK5gZ<(zixO zFSfT|<+ymk;*RZk`LQbD1@_U&C-hWQ%k?h39342H;S?h_KYro)mu@JS>^f&n@40&x zEM2v=cLG)}JvZ9xAf1~TauPLg(^7*rZ)C`+_bj&Gh`xtIPH7m4d9Uk65t-T5Bi}xfKm)~LAmKZUnPqRl5hU->kFrU#faus<6 zE^|sWlPvgNJGQ!e&yo!#%jRz-GQo|IjjvAw>?_JwoEYVUPdB|js^;+O;3}`XHirnM zMkNo=d6(;nSc?2*w$YQsct1C+on6^Fs73GLKzJZh z_F=XSOBD}BAJPU|aXVCk(~3Q~wbTx!kSxqz!W0!>YF`^4$&fLY9HZ8aE}UIkTWze! zjg=X73<@uc28xEV%51mQJjwPAg{pY%l~z)1_iUkDRJRQf=Fv{%jkT6VRMF)X>L*qEQo$>r-0 zx3^!R54HjGiB+}PSOrNDztV|c1&}B<1$~+H-M&2~EX*9F3RQid5^6BfPnbkQC?iut zF$1bIP)6JxOtml|+-?un5r!>Iw70LQzeKoG`m#9D-q~DzWru{FCw=UAYRr!zK>)~* zsZ@fyJ>_>Td+S(W zn$9rMcaxu8X_>Ch>Z;<&s(x2;j*FlL7xISAz4r2C9cJ-} zTE&j=;sjT!S*4OGCVqp-W7UJX33;!dPYcRb{$Np=y{7+K{O-^RpqINIqDM@p5EPL zTv`>P2$*~y66oP^t3w&-%yQaHPb*%ExPU$QTz$#B^UtrWs?}#%tTK5>w_c8hjBw^l zsI53bD?CRQ$YdKjjYsyD7RqGnT-~maZjF4cYb$3OKoJ(L1(kFvjWN==2cq87YocaYB;0t=~GBYIQ%=%{NiRK!ilT%YaVpFHSZl~@0h&m zsTYFjZmAGKFXfL^&nQHfxx;J*6w$s(#?jpafsoQf+M6lku zv_-QAg95p5$!G>10Kv&0*Xfk0-_;UeN5W~{x; ziTg)Kg?E1l$mdNh+3VQ*wCV{~{i<*}vf$bHsI7d-_K-(|PVp>KT2^aHU%%1Sinc5i z#zM43Xtk_i2}^{z1htMS`n0MxDP~<#fiogKJ;I43Z4|%Eb}F0C%TKo2eA#ekIE(Hq zoiPl^cQ!O4QMpC2EWG|#?d;p}?wU$1Pq&#8qz}RuLJCW@z>>|YgVmdh zx>AoGlKe?Ys)-&AEuoaU<7tXgtSS|ijz?jMx(4+e*pDZ>32PqQ<%p8%{_N00hpCjV z&o<^Hg)7*RqcC@0USmd9v2aZlAFQ`q!-E60D!H7C#b8-BVuPIm766@5xQC$4SNMrj zryI>|JF8#~%n#5%neHQ}$PJwEI$1!+iwcN^`T7R5${FNo18?OXE_h`5q4A-yb+7*p zy$0;?bW!mzkYiVhSS0hsJ_S!nlT-&K`D)%ksx%^4lxEbi_Xue8Khn${V!skaBSSBi zL&*ZCgkdh2ymG4cJR4uCP{`4^wPd-mZb2NnSBd5U&4PtDShr-QD_8o$n?85xgDeR% z1`40DV-^%E=(OWp;J9skdq(76qSBcQoY67P@|ubGl(v;rDEsML0G*G{Dc}Z6=f@72 zW1~uAbJK^*7CGZ0os*M=P0jg}LU3AQI=qF03S!ole|2a5sJrf0S{!s1mi{-0& z_L=$?uP;52Qks>pJ!{HhO-V^JMMrBD?6d4^6OVlS`|mGOl_Uw8)6f04iK%R#-y&a8 zlesn~rz0maHz}`0Xc4|ju-X`zriYbL7yM^ee?L51%71$B-{l_pSox>?hn{)yvP496 z^tgGr(8GF^ET`E{!ZDz*qL)vL%gl-Uz0i{d1q79a+zLfj*G3>z? z^(0q{{t1=wR+{yhk@3?~1dEbrPK?rsbV^B1gmHuRl44C6vdieYt8f)iPIp5@K9x7R zefh+b=BvBc%^OI&4lH1wjp{vZZyknMogilSkj)XJ zIHBI=7;1KV)KyIO9CEo4w12RK8{mao`2ntU#5YF!sNT4Kb5^x^S46Kb`*;DT(#CC^ ze8hZw&auOWUd#<#%<~rya5uRhEG&(V*b~VYrmXL|#Nxgfre%NV$f%oB2hqy2cynGx=1$zC3x zc~-AskanSQ%Hkew7wufz#XaiwFK{(>_*;^kJeSHj`7xfe0W(0Pbt%6m5B8YDlW z8QgZxVqZcmV`aPT>q~aAFOEI-Sm)>#?=2uQsn6J`fP1DjnNe`k04e=4Nxfw>K(@gP`3!AcQa(y z(2FUpHYSxNjh}!t{Yrx-=Pf+qLCA+$QnHMZ5n7Z~5l!>xf}PgU(pp}jRy1}z#J1Xp zt!u2q=hbjr&1ezF;8#4i#WEy|`4m)F9m}7?FXk85n~@CjRkta88Es4gCp`CRU;S`Z zcRj#{og*-KU*w87;a`inEb4Ijqt@Y|*stl;i5bY#(W(a;517`9)A9gYLoF{Nejam2 zjQDwt9Y(}6fuRaKlk})QJ9*bbhS{0pcMS}<*r5wPp6)>>hcWsp7x=mKS2$S%1#Sp~~MsBWrlz^-OKj1FCVv6jMc5?FNO94z-ul($$6YK0~dC5 z6d$k&KM})__N!8PKzl}tMB9YQGY_drG9gE*kP>t3s4f#@uVQ#|PV6X(Qv$4iD!(ar#pB@78hKKW*c}$l+1=U7y3@GMnNGa*Y{rngGS&!`L$9jWJ{?3_s@J~o zrtqJ%8o+@5)jze=eD~r$wF}TDm(8ZT91`?Cli}VQ;LKU$z!=@W{hRKp%4@53#Ps#u z5JT@L6Sfx3Z>u4vqks)Wr(VyxAIdd<7Pk@aNwWE8eUx&SXZr~bV6lmD1%^i4K!~DV z@@z|3V%Ou%E<7kbGo_G+A4Z+D_BvuRA2m;U$y z6oiKPm3x=N!+Ukq&7*(rP3f=EC-*4RB&0ug?NHA}w;fsAxp{s;`~3Dm1AqC3c#DNF zYSjA6EUgo0ZwDINpoQZA7U2gSHe=7xPKmjEIBnC>jDTn{pWOxFiK9rRo}OA5k!N8! z%{K5Jgk|xmhl^g6o-;Y_IgYDXGw2oQ*f)BXodgUzh$D zeR6TTI6O#@%e8Ui#(gdq>wc_Qtf} z5yc_=RH_1}fkQ;^!D9+~LPk_n&E)g4WhZ>hmShTwl5Qh|*Y@-X&ns^fzQZ7Peig6R z*(~rEg4J9YSFjhBmGC@ltcyk-(vd`sIeLE#szdUNwwM>E7KJ96VuAwwJGj2>f&`)-pyWk${`I)g}r1W~ck%vy6bQ?Sygt&g~q4k7b!WO zWZcc)fB#=XUS^nqf#-8RM#3{S-Ix#)#eB%3@bO-jEaA}6NG{-ia&$%t*W{b~BeVHg zwf2L0jzuoRSf5XGT z(CpynTT4sZNCZa1Qd`N_$B6w>9CB(aC3tu1iOqQ|cG%OQ=^5}iW>8xZpQFKRI+IUQ zU0Jk53-p_nfjWEfle0qHfuVZYEf8brd9}uz-YtxvZ{sJfMOw}{ROx0E1sqs(C| zEWvujVHG8xWCxoTn}u&)`KG^ltd6&bH$=oONfLgGNqwOuvjr*M7A=$2BD}J1Ql#Nz z)Va|9V#!V{LU-gTF# zwiXU=6vMX|`kUgP>S)o>knn9Yb>DiM zxtbbIKP00H9V085&x9&o_!|E$geQmE+IH}ZcsyD-fIt~Ifkt$XF6IuGbBj+bF6T}x z=E^|_)P6hSsWqe1Jrlt7Vuy`K2~F?t&vz6>y>f5B9naInyWj$cq}xTO$cunQg#ax> zvt*UmV6&LCIVMoh+`D-`gF{aRau!2Fr%qgV-ODcz{WajFD*}#P<5%2p(b&F*A*$Mk zhxFN~)|lR~`xrvcc(h=EFCNiM=HBv%7Do%N(a{o7xtuR9=2sekYJ*YQw1+hTI-2IN zWQZzAaytfEiBkS5ah}TT2(Gs(1;kIg?CDO$7!XX*tp6#u1&>yypUpsGmL*Gq(Dr(Q zOlfLQGI`o)%qS@{mBxm%?0|5&kmH}!liJPX_w@-+$7;2)OFW}uJ=0*99137!ug;UT zCPbPnRt)z0(4R^DJvTSE8Na;h@Zstln4&5vJ5DK~sJU5i9=0({f7PaGG2Gp_O_~h5 z4WnPt{p^0@oEATs|8k`JQGLF)xtTOIG+dvOa(x4S9b^Ier!)VkE}%U~p(16Z!K0TZ zcEYO{2h?-S>?eQfp@`Dcb9Q281Cb;Ty$4PUOo+;vo?x2D^w>x+Y^1;wj?Bv$On2up zZBX9W;ugz9n`Ki6`m-F*4D=a)%s1FQ*+^`}CLib4{bfJ)sb+o5>wnyN# zXsNvyb2hjG+yJ15dCffWnda|63SuiXK858+k3oV6L}?mrp7CfON;^=`)F%|{zUf$} z<34(QR!*9+Faid2*SSlU2w{tY7PW9HbyQ2FTFokn%q$Ny**P9X8GA7G;#=jy# zt&(dO23Gm|6+d9*83mWE(deU8HcN3>iH_52G})Y-sqxz!zKCRnhBAz5Ie-+A|B@-) zbxh5c9)J`)b4zP?#~NqnaRTUgAzJwLn&Ix2g$oz%Rwr7!*I%2T85EedV^5f_egX00 z?=vNGub%P2AFj+w%U@c&7%xYBUgZ)^_wi_aqw?5s=3)^$UWq!01mH6-#e7`!ic9Vw ztgIRIm1$u+*E^g(C9y={k2)G@VaWnB<2MUX!3~LpvO39@!FeJHB7W~XA635!4`zMK zT}O`&UBn)~fn61{Hnco+UEfh*IeYaG8hMs2z0LM@%nvN{DXz*lK<#faCp*^|d+Y29 zl|Q3)KX;KzwM)gwe4nTwqyirtF_zdsj#PX^zb2biDyvF$tRq+yPnAif+G^C0ik0+lm}3nC*7&H*3Hn8I1)*~@ zD4q|U@k(u*DCB=63E%F66jv023!XTm$HIgDg1C_m6FDqsDCnq}NHRpYn3srw86^Ie zl$WGuKFy^LdWdiq5X0o zd7?x}sSIJh0f18;W(kLe)_lUqYB~yZl$}!qcMT_j-dUQ%1;#nRsl^noe(qQXFfC3DnEdp-^s-~H3exgQ zj77$ z`U7iLxh$rH2e4)n*8D8iEMgcNTC}p-mr3nvHNQ^I~NZb z+v}py?GhA(G{3yD;c5H+(h~W5iq9s0r~$&>niXSD6ThrSk8D+Ax{yxJ1O-OE^CkD) zB^Pg+T(fP{y{^4``NnwRH>`)wIJ!|hLsV^(jO?@SPEs(mz`}EeVknDx=z0fp_05Zb zlN2zJ+h{D!Oeev?L^I|a6Wz3@D>~YDRIzzN*tbL27rbvoL0Z=`lcYiT^2c7KFXuux zf7-|Ia?f9U!MP{4?|2Glb(z)G+7m$#1*Z`Ntcmv4(pe|ykBg#xu2S$!FQaZYK9{8a z**zUHl=9HSn^~{ypijdK@M#+Ui}VH_!r*qgX2xVpou6UKNwMJSiDnV8SZlWN7At90 zbWf_#+K2LOKoN5c!j}!a@PY77iqQyv5sV|VyaQyYPY+(P_uvH=ec{?fZxXnIv=7VV z6X`|Wvc>p0V$y*k;qeWpqqs;sf*qhvBoR7`QRdt|4j2<5ZeDx6PWbN)kN+He>z<07 z^rpB)4x4g|Zvc5ZJy;hMFZ_LPgHSG4RW9U7@M;xOT{c_kx?t#>qQsS#MT$XJ|rkf@4iYYC^5uYO}_*Sdtq9wAH_~S67u-t$r-GUWc)- z%J7tAeMfa|{Q9hoF2J%3RyL!C4HgFmR- zX`@B_pOlbZFj=ZMFR)u$0-R&#EJC{=V>YK&q#IMS?c)8ozt}==O=dPH7Ni+d;%)d9 zqN(#wPIN|;068pxJoXNgRC)W(1G^7gxbyl-=l=ct8R41qa5^x-sj`2I?!t8b)l)#U zKybQEiA%iYIQQUaf9urJ_Kp)LXqgpaJr2+volQZpjHv~OP8SGn+!bs%Y*db&VFF+d zjR0U?${*V4&Z^#lq?2XNtJzdl>AoVPc6&n>le+4rBf>l33M7!;J) z*`8*x_y*`)x&Ys}q7rVXvNomPk&-mui5!tktV^c#=rR!@0=H$4p*?gGZ%p5qhux?3 zKfN)U`Z=qzk31mU62LSY&E?zctma0=%CVjG1O>fDQ%(um3{qpnTM~@=m3O6$Wd#K3 z?83zXx(5AV0J)!e&*^+9=eC^}oPY3wbFRyA9Xtp*@XWt6!i_SSvIp}rqX68+?Z5eg zQ}0*}VXi}0)61YW6kgd*Bga|dUFgx?H<#XKD;io=FG(jFX7^0{nn~3$Ps+A$skK>_ zDcZ(F84%#|tQ{`4Mz}Xw$W~?ZukvHN@W#0~CM;EWO!)U@`*$5nbzOZmFTCGVj#t9vM&iqb)W*H>KGh6n5H$41WC&zWt7#hVPp%~F&h>o<<%6V9n-UzOz+R0n2 zD*sbDn%0=mTQtZWGyDI~#v}D`msmCe1Q1z#$tu&9))j-MrFqq+wU+exX5o_nQk@YT z#My*(Bq}Z#7HNRo0*V_mu5XU_qZB9nW^QS@IpWLQ*8+Z!P4RZSW<^rRQRC{WnAo~F zl{_uLsZ|as0*|H!X?2z(>Bh^H$^b5(JZTT@4xg80>o(XHtS(I~sBepkNLzD87nHDI zb-LN&X;zI%%;DCTl{xw{hMjqG=3I^18LcZzaE3+(2HGlu*|vm)xTHvXVx=Y_KQu(+ zXMmic*e0|;Jxatm_#@R));sIwX|MDj2v_Wx7L8X2bGO-4xpR@#TE^CmpD*^^>e-IU zd}Bstq&!5!sD;X_UNqM|l-ifA)CR^1S18nV;bZKryVwI`=bgudB;B|3oC_}7an8vk z*Rf+r5DE9b0czlS)PzL6Oypc|*Lkb&dMwUh=vueAYpUzwi@U&iwEltWRwXESHSJiR z<+13sXVWtFG%Y=$Zi8SrMa>bZ)SAM#q5Gu18AJ%62B)Wi-{r%hr5DS>*XiligYfsNg^ih7$%RRw#s^z85^RxK= z?E9x@CDMsh3gdN9W@%Ua22k`uLZXEKM23Whq9KX}^0x3CcMMQ8(DP0ns2@I~csCqOzbj zB{{w@$jPr8jSdf!`*A=YV9b0qjmcBJEBS6wG#*dRmV#axPHQYh5}_uy9NC3*{cO`x z(oUXpoW)aUg40t(EKIKcMn1A}zA3ycpmxYf`&?zAhRnQNV|uCQuz(HbQWsN-8XvYgQ;3eO5(Uw+|^YZ9Huk3;0Y{Y$pN zfTl8Zs{{n-e)xvVn;*_!a;)0lH=vnTD!R_&K0h?r)AdN1`u6^-#<%TW|L}XflcSTZ zsXhzA85{-O*pX{3nYKQH(2Pl43Tk`ImIyJK4yiJb(gQ44$=I6R8Nj??ww7<%yF8$M zY_c9$?{a5JiLoNahD~uo@3|kfUubqNTN?TA0Fsl3SfRP`DxpyI@PPnwBh)pF-! zA9r9Mw5J0VvS&}!Ab;X{6K`6(bUmr>B;?TIhW+K4H*tC$Wz#)OCR-J(h(xekFo#h2rd|Ee%m-Q~BoE zgbX(%$!sxrAezZk+evN)^SRkNe{-O$x3K>v^Fe24;RraSK0hq9IG}Q9T|!VG_|va5 ztN8y=_8tIDUhDrj=M7n`69{1i5<&=J?E6BX`|*n7g+c0n|H5@QLmd`J8NXa3pK z$d(WmsS}03(aW;B!;7Nw+k8p8x~uI`k|Am__&(a4HMi5)*^rE0NubuB>9|SfO|T3D zFKLLc>p+AtDKjr3F2}S-b<<|`uC|7T$-)QXqX9~9qKxP?3Wl(Z7indtwXL8eeY)8g zF+IhSTtC?TvsgPd8y!wGdWXSB89z9lc9ac?ZME9%C627)6}%T4377=BjacSkYcam;#}TwTbTE1L93Sj9pT{?X-}kDN;!gZWp{Yy z1R^OA6rAA+Z*O0D(@4!|S4X^F500g3%N=6Og?j4ehwV6o)(>DdLO$KixzSFd7RnUZx#?WL_L{+nx)mT%5wr`Ez<&@?iYQXnn zp27=uVl;Y4Cm^mgM&js_t4)p)V$z+|vBnV0YgJI33M0>L+Q0ry~6QV^6NC>wNAx`pBSl?6G}h@K}|%w=&XL;OsCqPcve4 zr66FRJ_#H@;ev5-WqH_F8shB=d-mCfF_b-^}WnaDm_X0db!QOn*m z7AqFm3ee?K72*iyt|eno5N%9NqtpF(B7}J*-e{pgT(9TzKAs0on2{|r8*?ldaa!_( zO|Rl_TvB;_4qOZJghM1RIJdm`(g#MML4cgbWPZ=PGQlT}|LOOj;NQRIoPMJ{J9gEWW}zQup>Oq^l07XbhT2Z+%J26nJ!T$o+a_mbN*AyNx&yKfs@{Dix z>Ccq@EbKyN0-lXLEg_a}NjbH0{mwlbm)*^|w#eT8J_lt*$^ZvCMQJtk`>!Zl{PB*4 zk+TQoXN{jcMcN0hFYx>Pa@LT5>Mde z3KN$E+rS1>+;c0(Glg&TVdaa$iTig|qDTgd`XrrynPzzBbXgl$Oi3#-W+dpqq9wL; zgc<{He=ss4c~G%2FCj>+d0q3q%2veSvssv>*2FX`M#PT|M_jSwjBEGC93i4Xz=YlV zN$R|1Gj^0XcJB@fOkm%$w;ELnzXo50pjPXN_xF@x?9<(Xsi7b#B1};D*29CV*65=f z67|+e-f;=WA%gqu`yNo9rSd#=?w90#ZyO>5Jn}nxfK(s2Ls!`-ocTcSmIfqC)(GMR zeQ|^SRAAtlhhIyk7$1(neRzEd-2E}&SHp}I%Z8aEL`}V`OL8wngr^QJFlJ^)K^FzC zJR(Mq7RAx`-rM<<=0*OSS*+2Fo4sK6(EAslq%>eKrGp#rLx{%YhAlgH-mvm`p5vyQ zMD{cLFvAE}5DXT9#C+wZtwY5uu|>>o*u1$R8^2_ULmP_mXaKFUbEqa5H3~l<_ciFf zA?L&N&7$P|DSQA$Dx9YS&Scq?sOR}TVa^y{btG$4*!h!Am`a6m>e z+3>RZt(Vn{MUqzQpr26(IaT*V9iGHXxqQV&?=hOmt~TEFO4-cuCEYB!!dSxvv_`$a ze+WU+1j{5$u>6S9k61ObAF4$0nMAIS6DvtfbliSB(kIw=Eeb*B&5Q)KV|l4gv=#J5Ht z>u0~w|1{o?%&un|$q2^~JRIzFTG|%riSt$>hI>%)1lG-`z)nVVtQ~8HxakwQkUIm^ zZx0)U$cCum{ParQm%K#SC7=tg?!nC&5~@ar_p4`}Nl~CIuu-Tld_NR6H9^7T#O6tn zlWHKqA&;Mp95?>f@E5aIdl!zkw3e+=6F(i@URO8xU+OPiFStJYY-C*>d(GE}yt!jo zW8}ygZbmT^7=5=X`8YrGtE~K%&X0RuSgteV*v6|>`boh`VhRnWVp5W>(O8&eigsFK z>Zz`FUTtiB0v`3T_*L&$Z@09W$$X@hRr^!w&u+9NBv^EIYcP2^HkRwe@;oZ;FSWsr zpXasEYsLRxp3yJXCBDIV$CWZ;jw<~b5qw|&4?ZHx^(4~IN4^gYD_T|_5niX6=xP}S zoKZwYrl%AdvlC1Z^+2G%i5aelKs+K`r%#_aaX+&vFW#)V?HJ(3bgZUODo} zv#aW`yPLf31RhE~ey>)00<-c4@EZj-4LJnJg2xPuD_JJkn*Q7o1qfGCoOq2A7W%9RCn8 zocTXu-GsOa57j46VEe0ssz1(&qcpA|hsMRx&&`o&*v9QsKjSDqa@Nl;A}Th72G$R; zt5U|!nO~z&sZ>>Y#i3crp;8^s7@yqSIpM>sF+TIuOIp?lgAwVrsuISUMnR2Jpf?86 z6VX`@kxn~3O{Ax% zQIS3Dw`sOU5CS?EQc>KnDkf0p?X4b>8F%o30I`G&7b`|rYE%LBkhq4FG&(*nFK$Yd zB|5FX*8QH}#jSfD!MjStJqyZJN=>yIafqf(6tU8x%O?~!;G82sE9jecmD5793nitM zbdz&6DxFTdY43jH(VpQ7q~ry#N^!UOq4aJa;e>ZJd+YnPYK_SmtyYZq?w@qooX!JJ z6v8eqx_n&veg$GtsF6UPiR5Aavl<6yIhc}kFClYD`=Sx#EDy=*f|Qo%iglZey$kFJ zP6Q&n$VQF633pxOf8pDoctrN~uSZxc-kPv>%mmTttj{2{vVLawuIDQ_PvG&yTNT@} zzDUmm^gp?3fpvZNRUn0uqnX@50E)Vb%p}i4oxstnwjN$;T)%RNx5V)a$L=>jlC;QY zI*A)SZ!*;(_sCCcT$s8@my{x$;6VNB@;a3|!WpSjjQIWox~{GJroWZC|9kNrIO7~^ zkB_cFuK?_Q8DtvY`*i%z6=PxGZ?eUJ9lLtJ0JZQ`@Z`xaS`Lk@4jgNpH2I?ChT+wg zvZ~$Yv*ty92J#4(RNj`Dku6QemYNC)ZlUDI-Md#WYF)DI$tSwI=JnQ3nzpm2wR_ju z!THc(e;a0rQF@0tBUGx&Pkw;1q3}va?@JCJm$`8`J#@dcBU2#n$_e%fg?QNfaPlIb=Z>}?Ticp7^DN)9SAJQk z^bVaBu0ZcO>lfeC)vevzoqJo{JlJw(jX{`Pm>VY#GcJs>k}_C4MtuzXFy*J;k>q2#7TIg3XfShQ$zXWaIA3nt76UYu4ra^2MK?SCsS7TJqGR#>9Fy$w#I)*Br% zo+1X?GRZz0IIFvSRIvj%F&vN&teJ;B>*i)DvegWhoPKWRSC&rg9+hANi&w`9*NpuKqH5)1F_);e-)$;B6cIZon^R_&DApr;wri-#Utc~%mzAx9MEAjjrqAPafxedR zhhQ4Gadr9;m0^(lBw&55PFLhZZdeppI?FFGP#s~+ zB{!t#Cgg;Kw58@aOz{!PjBRC8lHP)Y?e3CH&Oy35B$dlibmvZ z8UMRFvrBgRx(%nRv&-Wtl)RwInyLed1xGnUm(j?C%{KWk4o$)yL%Tc`UFh z9w`RY-W4BCyQJ)0Y=PJD%MVI^T;7rS3fJcdBys($z*rlaft&Nre3IX*SjE`o;^>%O zGKFZrA~P=)!HMOa->qA-SfkT<$HbOnve5aV>leZC$9XfKb?_kY-6H7=SH4|xh;zK> zem#g~WY%$vw}FN6W;e3t(wb6KkkK1yDw;nxlh=4fPcH9WTd{nyd+?Y_(%WIXCuK!G z@p(E`rO~J|*6H@=m@R5_?~Ac!9*Q(Ch_jP7*|ItN|3K~~iz6@1-+l5u2__r#1P&AT zpm~ui6J+~gLYgF?!J1L!BHOfqR$)hKnl0HC8I?1$9>DbOS<&hK@_qBAe`~+)?InBJ zsRT71iOY36v(SVP0Vi{GhVTcPj+D4#3Z6wQ&(r2nKiHri zx!aFc1I_}FVXr)G*->#Kdlh82xIQEc1Bvk|uny?#Ss_R;gyIf^lv`coaXE3Dy(eFV zlqj>w)Y(!!yh-WmZK-F2pOsrkJvC@H*=b}O_KzB$9uU(bYUYv9gnw$tFZ3C2&ndeCqGAm6ac7j@rDouY6^L<+0m zHS6--_?#P`%Yeu8PoLzy9In$#$di~4BDMaAul}1`iLd^Kz3Z9`X}I0BQw$AH%}9^5 z#YIqIH|}?s-|rsL`A;^VY@zHwq+E13#Q!9QMQEp^L9J;ocNqbew##<{e+0micr7dn z4hy%Qxb6gpK_>7>@*G(VKlB6f{X?#&6~zneep>O~+h?>LgTv#W!hV3TmxFLxbh49t zMDyOBF?Geu7mJ}&jdTRq816my^6ki#=J&Q&98#VKjO4WN@G>^gdH{CVF| zq}<2xc%_3ho?7a;iY`r%#eMY*Uju|E+}<37eG8nFI$isqMcf8&@OKL+T0;x_u<|`< z@gdwRI@pWVbZ-%@V(j2Sk!bOzU&My$VtS$q#qkQSiGb@dY5i&NWL^Uika>ca zNVbTmfO>;=)?*$2zJw|-1v+wM7Tg&2dTq^{VfzZxI|O2L*Qto<7)pE)#6Q+~OfDfh zHb<+S(}^FyiDEmlw%UYf>Rv$`_;EC_jO_3zxDfyNSiiOGbo zDM8)PNUcsA6N4X|XElLmodnOq8C)$bxctfq1fokACPfG}0KBNjGMa39tP&t9eI>IPZA$infKWouen#q%A9z7~c`j0#1Sj zJiKD(BuD$y1w&`&jV!qHxTd-Xt9zxM?wQ&3+0kD4u6}w`%k|rg_DKf{}iqoe~-~e{^K0N=sJ1MSLZebb!NiKj7JnEP}|6 zoVHoH9g%Ud*^ah^xZc?j5tx|d))Q{I`iX|G(3YvA)zLs{al<_P}H9|Xu_Zg`BU{l zF=QP|DnmkIA|ecdIs>7J69$Dm7Q-iW#<286XDX^d*ed~q3h!O|Nls1@er2!CPN41N zgug#)^*_lA9yXE$i#{kQDCQGrfGW|O7<%$4vNgzhBxXKG<% zgb6(*h?FbHebBy~_N?d)V1aIiT&AqWYz2BqEhekTT$W5;Zzn_8IXLtAXLyhOhZLdE zulhcd!z<0BAr#cWMZHAC5Wa)&P3jL_0~7;V`^x_r6cc;*xNcO3g=DWT42w{0*WT2-+qI{6Hkr-R z$ScUzIlsr*uwcp5)@{y2bRhX4f#k7gNfmq0iF>6(Pv+~4_P~6_Yb6VGu0{ZOek+3_ zs29=0p+gQ?=cQ$6-_m|^o6keHEvaoJzP-oDNH&_>y{~E7_yt1-SI&06?2K`SXkI5B zY^SuQY-w+zy+Q`;O}}-6&B6B6^s+WBIedS9Q}il*rzTj4V(-Is=gn>;PUmxDhWGUD z8eZUh4qpd;B=0rPg|z|C_z7JFIi!*#RXM$J3BUtJ3XO|B>04N|B6_UduDxCRkc$+! z?qRqP@&df7@XS;y|xl8L=!D!YZO5tRSLgRT7XOXm_Nsb&x0 zg74=w6Z|arbjBilKet-0_`*6&_Kz zsAZb6Xryt<5LavQ#uoN_FI%J~TYJfxOP^I&lCIg-BiV~W2Ne(&YlLeCeMjSqbhTL# z5$b?|fVL<-ZPAB)aCg>-11#$G*PRn4ILUpa^32JOX)8w!9>Vt}7;wK2xOwf#AkGQ< zs!u%i{ZM$gvx@{>!C+Ym^Xd}`!(|>|ARFiA&mqed8bxG8Zp9#Dg)KzGohfLpB^=u@ z-JV1?Xm`3c*CKw^mNnPC($%1y;Tn4%QJP*oTNoZ1;6xv!sJDn0Wi!9pwQ}b_ocs2P ziR0NHB*Oi&kdCnv9Czg2+I*i?sLM)2B%kj!2&_+~)JwZ6^#YLtt~h6y>3n>f>@-~f2`VO+$Wj0?F>`6=-!hl7 z``@HbKaKwE)%0L7t-e=eABq)@{repiB8e7Pty;w_Fct2qrpQM2_DTa!<++%gPndyU zpk!N)4gau1yjG8EsJ!3_li8hUL~pccbP=7vs)et|7`0z z_NbZY=J37xFsyejiOWGkRMM&(cR0XhPishdFDXXT+`I{q6PoIyC$K*n$&iSynCVT{ z_SlNXj8Q9A4kIIl=$N!sdm0Bd=I1wZURsF0k~}6aRYd_FS+VE5MAGTy?Lxdqa*;}I zKZtOyi-7IIfW88;u)q4ui!Zv6MsZ408#OD+6*X%e(TL4sJB&@GiPZg1*uO-}wrI$r zUb2|2MnjaLjXQSWM^jVNYLTThYc+w>(Y!2?Y=xx%r!Yh;b=1^2u#X17__(yEd59kH z%E>C8H=s--n5`k<6`(P2*r60lFrpli6T=mqObVh)V!FS2Q5@xRHI1BZtgA4wQ5y0vvH<6Gio4tzqU@L&&^E;7qOBeC=1r#`x~6DbTw{_* zm$0cJ7I|#8#;J{JWx%u`wZbx_vDjKrFqFiLu?_}m@0lU+=!Sj{evpLa8gz)3$12I7BY>_XpiE)MzHg%q-Z zO+9nyt~+|!5iMEKE39-bEFZaGbSZjNEOqjE8dbR0?YI~BPg=Ofb|uSXU`-tNbYRdA zhhvYIc&CWx8?>C_N#5)2-LQHE8epL3pXi3SUVuO4@9tj|tADSUws9)(YZ>fGj$gi@ z26$ht=!7y(0h54Fn0hF22(IIik12hgc;V~Nt>V>QvV+YbHZS@e#nUM=7*3>AB)hh7 zc8>4o{Sz)q`(?liBJ&wQP>|W3i(yyz2dA9L(3Nx=9jb|6@ROCve)QzsVxrnfAZxslFZk`mXLU;ENJ8% z@!U#ct1`(UpI*xR=xatT1dtTm9j!S&?`A%mg?%0li@^vRg4Ox zkIf#mJ896&&8FECn|8tS$0MzG2_ZSCwDX-ai4CldTxrZZv!4s`gfGTWPk~$=J1V3q+V*ws; z0!4dTY9QGNs z*MVM-B+ub!b#!*Z)>C4Z&P|F(aep5#6EGr?cLJ$~Qq>@ZBw zW1@45c(=H3A3W}#ZlO!RK;4)ZA=?0pOZbfIo^tFIrvq~Juwj`!LrD}y4b3$B`O-Hq z6&9TmB28t(s_(Dt`ErvO>IDbuN=**_#$BDQ>z z9BBDtyu(9N>)MP}?F)b>}Tj@R?7w52M84R_5 zyW8QKaE`sdnF@EkosB{CTD>DcL9EswNVPBm=TEj#AATUGmLm{%j=>nhhW6W;l9naS5FDv-)QpWBRS}3h9-a*M6HPs| zGQ|}US@hC{8Fgf8jl%nepWIVvoDBmNf(;?*rdP(Vr9mV()8EhZd^x)umH3Y)xN96# z$=mwqLn!?)iO(-dE!NfMMw%eRH0^xUPZo)F(DKKfEt7f&i(@v_`Gw4zde~}K*laQR z6_yyBgSRBZk0}T^uMHMd4cN|=9fLxN z7priW3lY={yfzU{2%=1BNlC`Is2H4y;6DK~sO~>T>24bQR$ga;Z%|MyTO1JPix2#P z2q-QNg+cy>{fqsP8X4s?0|~$8Z@!`Qeg{1b=`x1B*;zCwBzrz_ zFRmQVxUJ4}fQ?esy{DB|Kj#`i4P5txi6t*a&fn?tElV6-xIZ^er&7~aP7$Ip5@Yb# z#Q6cm%A`pSKe&y&DrN*cwNaBT?w|2}mCKNz~H3;t=+Igbn1J{2SGhQO~eF z2=HsP!Q>u*9xGf1==yeVg+EDu^7lmJr=Lz9GO?tsYG!6)YGp@rKv5yd2W7W_ve_@C zXt%)kJORCJ&tuQWAoBrw3=bv43X*IO1nM}v*Ci)TE}r&#JP6zGg8RQ_MOYMG%h-kh zEqVBrhV|x|Q;1Tj9#iWGpHQJwdZX+^@c)%Yy}36?rRod|&3>9)PKk)97;W7-;S~)@ z3-(17@!Hg`R54KF^6_`3b{#caZZyZBRqQrLT#%!_Vt#&=H)bB!`uHIFt`xkzsgKs5 z9x)-63%Pu>Oedy=hA4vq6-Hz7Q;Dhbhl>tNC|YKhj-F7^#fF*V>?Z6*Ur$w|7gG4I z;dA}?7h{(-3aA!x^cA=$dsDD;T%q9qgp0Y6{4AS*g3n`<+vryL~+~>k2!EeysUo zT;gEUq`(dswxO$c?b^8U4jiObYasXIAfpuu9HdVHFIP;|&!}kDkZAU;56N=O%1LRj znvr31%yNvAt2t)0p1prOK z_~d!pusk7JJ%LYCbLo1=_4%HZ*K1N=dLc*~=Of zv9UQeR2Rf&4pS)$hGUNRSRVxm&nO6voaAk@hZY2BENN3Q)(YO7w99*8XGFpq5dnW@ zyjK}K^O8Nlbp(&Z?K5Aw-lV=R8>K{+bMj}bwtfl+E^q-2F;hzVs2lF&CQ%=3H;07< z2ZYcF`YrqbG?n^IjcANgkNni%pcT7u>URo`%*>rp%c9nX2Wv;&yNUWN{!kGJo%kta zK%tl7G?hXTOkm*L?VzW|Q96!&=p3J|ed4X*>fmM}rA&yymz)!WlcN}We0uW~lo6&i zt5YjT-Z;O=X4Isy-z!tOF2`%Z9umGsHzwKA0t#@qu_PM}M2#fJgk`BzCkmel8@=dH z56qm_(cR>@7jAQIObOEG?-zap)>YseB;F^fu-tnudn08Q!EuZGBm1;6Zg%4j#3OjG z+*2@29BJ@JJ`@e`4_Ug&5J0<$#hQ8c{!EKiB+hDYYfMJ`Q;{=e^e^Ko5lC-)!s8qp%N(=;-Vz zvyG0rWR#?mfD67uhwZV<&P>10 zvc|O37PqxIhPQBgI0k;W)50ad$8!U+IG4b9P*1KyAD>YSBreRul~^fsTG?2Ib_<_f zpNm>%uke-7q#g<2pG;9v_MEbCv{#(v?h^lwYQ1f=k^I=uz}_$Gm^m){Kt!9}G?fQz zzwd`yFHUzm;-aF^gsjO|sfZAo8^JKUNjq4`4RPdER`T*~b4E4gjmV$!?z>|lEBJiP zN5JO-;rEL2Do4)<=-t;@Nxz6NAo|ChYEqyXyU*>kT*)4lRu4Zjv;(_j`^&kYh*Q#} zMe-ROT?X@PE{S(%YDfXo5S3?gAvq_j%116;>f(u?RM_i9zv*9D`=ehEv^#ukLL z2J5TE`nDf*Ptk1a)-37T+qHVNg6*ZG3afpP{<>E<;6;D<+r&BZJ33}R0MePx)`_o+ zc&=?|7~YVBM+2*grF3x&@=}R@_;Bf*cpl*03U1}f9bfC9rGOqRh%>=~G4Mo`{5bm4 z$>e67K)XVMvaqX&j-#T%B4yiptiBI=>U-P|)c4erBWx2T==jZUr$jKDGY6#`)8Y~y z##~>F=ban!3k?XcE{+etV8B@62X=qRr}8U@-+AX9av}{Sx2e!*ettG9GEy4dY&(gM z5hdPKNZ)_d<(jo(#_Tgh3v_f8;uZD>UcoXwG}d=cy!I%A_jI@pjRgBN4F67NR^t2l>GXpzRWywMTl;~QI>Gs@3*uBNA> zx;i=`{hBj#JK($4;jr7#)An>@<9XiN_BHk{DG@N)BmB^Yk|IJTO<%ow`aEr*GnlVU zEyd{1OE_2nJ##3s%t!M%MAuneAA3MNMPeI$I=RHSv+Q}ixg{4WSF}+B`n$pQ;0W25 z%Jwt$@dw@MpdNh%t0Q)E#|~_`-Wa)}_y)~CeKp=%iJaui4QAE zO)_U?cGH#?DiPfi^__FuI$Iy+ES^26`UqdJY#F}m4YhWrHM7BJjYOhKdPB0c)qnRO zx-x<0-w>Oc9LL@plt_2vQ}|vXpW?v(UxcqB(5vyftGyx~!9AdH@hV=fD?)wXfj|Vi z`Z~$VVI@AZ7)Z_?rE*Lgzh3D}`vq__w%Z1BRCsOmz0tI|uyn7fB_%m9uv*O3C8vyw zLKcrGQX^TaDGYan^M5((FlAA+>zlka%bP;OrbR6GQM3w2Abt+0!V&~+0D#|E5w zbY*Yr(zOk9N+*n4nVOegC)O30=FTl0D?WM))x{oen2JZkVQV10CBNrza{TgT3JM z!+2*?D|BC8I@D=$lGQEt7Q~SK{F9d?2V#rdxv-)LS)V0yB>)rKm-h=;E%i8#~+9HQveTV&$TE zLxaKasvjYKWBi)Sv`VES*br5P(O&@yYqNLiAaYC;e-bBWB+_k1z136K*Phr&?O_WW z8g`AKTL)2!3=r_gSnScG*dt82M0M0Ca7SK)1J5RU(Oz@vQtmS65-=Na=I}&-c}Q}u zKZ`lbz>B^y(AXidCxiZda>IfXwa-7HL|zYp9Eb{`u-PTxFka;s19=H+ACop&ux z2n`FI6cjk7VQ)hN$!;jcqk+AE3is61wMj`>J3+4S-=%p52zBIH(0v<}%$=D*`Yho~ z!LoPJ!2n&L-A%P&{~WfwDvzvTGi;$@ZKQ~tWZ*A69&=iX3G!$uxB|GrK#wR_LisE}hc|TN*Z+ z7EWTKV&Zbs(J>b_1Qi-9)EaU#`yoDdU5q`LOq{rRX0Westb3K#FgT-TFlSz2mBTkAQ0J(MNl3Ji;q%spM?^YGYDf3+fOCKu&_mNk+K0^>DIGSs zqoP4d??71inJMdD?c7l+?_MTIVG++snMQPQy7z- zlrpEV&VkhiJE8(;R(3Qgl2=nIjs?mB=j@2LJScn$i3Sg;B&3v*kdm@_fo_sK62@(K zEBf*+cQIyud+*ud-Q)J&TJ7%D2+iq9{$Q1Z^>dmV?Dh7XP@F3$R#Of%Cn+A zvCEG{!3{awnYTld;+!1S(McPsNQm&%w4>(dmhXPid~8u~p}wr3%X&sJTPtkOrUB7C zTd>%`jQ=i)|4O@@Jt~YTUYTFgIb!Uz()nq_JN6%5(B84~92wK*RM_otE9^0uH>5Ya zTDG@ErlTPWVm#Pq-rp>_f1hQj}RmIW{)0 z;_=u?S!sq_wU2mvFBH6m0{1zkmfV`B4-Cp}&q(af9z8Z|eq3SW*0!PJ=6`WDJ9#MC0-VRF5|p5ev*-04ms+I=9MT@! z(=css%<9?>lgm^*oU(VcWO6Ms?!HVcUmj>0w>_>`tZrIflANh#|I(7$$pOJ{)lb>b z>YCTKV)BgpUb)4!{;ZSK1~b-EvplAtciq~y4V!OWY!t^*-A zSh>3q5~Gkr_XmPyguQF#ZRU>_uX@z9t!-}U(zKZi)0j3OC~sMTf!6*?lD4m7FFa0# zxZEHe&2G<$pPyS@o7HJ8a6YhlY}3vMrY-83zpdr?H=|=_*yzl7YbK5mSj=ae@Ysq1 z5ar4kjSTo}6gy8zLjRjM;#GI*JAFB&gnHNO1 z>wOn!0?CKW+dqJ;C<>&8f={5!ELW?C776QDxhJQ@yV6IGN$oP_kDR=rW%1UJgALX* zH`PsR6rBmNNw&1*#_AS7|MB4SVbIZd4h840(LCFc*Y9&ljAOk5^;PFRz&*;HI(R>_ zE4ZqTpl|%lBqv+nUeGSgC~AzGD~{h7(>#r%5%FHapr{T9%J%IP>mF*VgmW(SPsDcG;1R;7wa~-rVFTqgu(( zd5(F!NuCn?gXcr5fTQuCn{;$)ydEBKayAG&>ku(GK#ZVGU>?SSUC|H}F@-Z@UE=uV z*11EP!{!GT4c4+BwWI{_va)XWNzmX74S6A>_Q^&3@v7Lo$}wwZ%c_Z!?|J{|!ZnXe zp#H}G{?MU<7Jnhl;b0-Uma^96AGWnUH)jrL$qYCzDuR&bR1U1*zHtqax`JK3!=M(-@Oz{og>IwaC{*yg}Pj!NKQ$w zFS7d1jVvxP&hd*aZYWO99ap_nY~v3u*OD-+iuWAnxDpDB-HUg(=eUBrDr_ZB3Y1k3 zE}8pyI1ez%9Gv?r6qBa(OmSc6nbJc}xE8w*;p*xl-Rz@fXA$PS_0+GScdVN^c~irz zRa4v#`UnR`##9Rhd@@SvCwMr+x#w7DG~-5-bCmfig#fLJ=xW`BT_R8g28$h85BN zVWO=ydSu}opZThxqrKUiS~4j;h${S9M%(e@;Su(X)NplM?#$#Aor&D5IWx!GVl^Kb z+@;ptQ+&c+^7pah*0;9KZS3(0CI>c_RiFR(woUsUa+)Htf8N+!>tJ?18PRu&`CsRK z1x4hCj@q=a;CanAgxlKJ#CPQ8bDXzb-X!H6@pDMWTn#84e7>(5aPyt-XT~0_S>}FH zvOdq?<7r~RF-Vdq1bnvTy_m`4To;1UG6kH6v zULi9_xQww@bcfGNuvosyrva84XZPKaKb^bza^#VP&DA&8PF-5#zMvtM--)#s^+)NE zBlJ<%1xmhch(}=WM=>KQ-aaxjETAo@BsHqdFFbolT698@BSZX5^p9>bDrjm$BHGcx zBM98|g^U|(|VS8t;Cv8Y1_vHaLq#}f9?k0sMz;QKW{)X zSs$0+M=g&gm7==_1^+|;%ZC2Xf}`afyudXQy^^a*eyYlz~;keR%QXQoA|q@$C~ zwoqk>U&BOAZ;nz}oubj2i^o(>*T-u$kulcc=IUu-l|#NGNV)d2ixqiA8sl$wgZ1J%fAM7+^04SPk| zQ={C3ncpK5^>92-lxLpHzGt5L-Z!UF_QCew37N!Cokx!FSQXE`Kdjh=`}2O+ynX>fMRGJs z%ae@ft9mfeVMA549&?f25{4 zNmbj7x2N~|k+0D8I+i;TJr@T(K=CBm=#0j`TCLR@J1jol`asLqZtAFC)OHj}W(wp2 z9aL;XT$bk>!&AU%0uHY$9wN^!UVjb(JY!s7N+L+%S6l0Tx3)GH+xalgh%}-vh@jRg zBTcz8CP$8$kf2g45f^_Wg&jAuDsDft3l34Ac*sX_VM%&j()LrZRw`qD*P^6SfuWo4NP-WxmI z3r=;RT>gmrFN&Mgi#TrIn z4cnmK^PU5RTwcm!2Ekh8ywSeQ2>7OL1JGLZyrss{+N>?#pRrlYTfQZvyB<>fp;s_blgSTsh71+QFL?yAW68 zc)|UMSJ717m89q_%-^}=s_Sa{MUP)a4IH0SnKjAx@-f4I@X{g0k#lLBU%x;C#}+M|y-mBpfUKA9U=Y3%C{g zZA95H#Q*oDa)G4#vh~~-WWvnBCcS~%pHqr;x0#m~=XgJ*6)uQc^ocNz6e@VOvAcb zQ&hLm%huj|$E^=+{^f+bgOU%iyl5G%Jb37S3Lg=~FK@j2+jelCN$`p9S7kTP)ctZZdA0m+NlwQEU>D_!06s1e_%kJ~)6qOrNB9lH=|jJYmHz zv@Oh6nJSEs;Hm@e9lb&e*OMViE;l()kf8_$U)5MmVfH|Cr+gLCC^KLizNE~D{C;M` zy%2DPZnID1?wU2l(C`4%5Ar+tWj>`R@#gp5e=fytcP_Z|IOhZWoK8sghaMT2PzwKo z=Z1#BF8OmFaDUn&2$mb)H_11>UA_RZDk(bdk>^nXoZM-ON^vx%n9ND(=Bl*b_WUIn zYX?AuV)@R6X{o!?atUAf^|XYud?`Rr$* z-@%>24}eALJ0WG@K>l!;@&L!5^&4r@rBp8O$!_}y+4_{`TMc+v*=_RmK@iGmH^RHZ zhvc*uJx=j-;Pvn1m-%Hbbw9`}|My9`V+ya4;A#B)IK4u<3;myRKP1#s!OBez!+v>X zC*=!bOjT6y7vdK_u6=glQ(h8gmCLWWAHx-x^#?Xl89<0XOCoPL$uM>n3SM8WF`u4m zM*ipjt>5*@1u0@EnG6G@0Y5WOqb+}nJ~&u!ala&Ak)#M^ow6Psc_bD^94d`p=k9y- zH*CpLsT3&SD1;YE8@AU+A@$%1j@}m?tk=sIq_gw7FLr1snmv^c>+tapYYF%BPKk*{ zU#yrZ(#$Jw^ou;K*ec0$9uou=NHH=GwAVXL?(xr){LEJ?s=QbBSAbTBUib3Ov2`95 z-N(J!C(cLs2TyH>K-+fk7Q=x}aXKB(4h|(LUOYlLnL2=RNBAd0tw*3jKj3uMN_zEq z_QGndPpC7(7Y+5|Na8PWee<)&j3w0A@O_7V`WO5s>pK2OkFVT=2_EJHOn?O>n8vTS z9Go@huw}#Ko~XIeVoMa;>PK!Z3GnexR!P_~SYD(1Jg&fwOE@EMIKaKA|&|x%q3XW9+fYx!T?y8OKG>DtHBeg9=d9+&>4P zp&gFmH}*%NIg$uLvkU^se(UA%cQ$VCSy{WN^Q7}W=o6#RXLKj@RRtmuHpxOz=GgQD z1!A0C4iy;wO1nTZ%ZnuQaI7_ZdarTTgj^EF-u5Bg0Li@^Exn;*$WWe;FQo0jq64fj zEykiyVfvX$kuF%n2uBt7bDkwgxFif*oOA}fhVpqh+xX=25nQ+6;@b|j27F0!#&Gca zUDRBc^;E7X|3W`fJP(|(loX+d3vCE6gp{ow86F~D6sb-}o7~Xt@JyGX-z{GROIVmU zD)>XgEW!#(PrTFB+v{36@9n=sMOhleOI?EY1m;kJZurbcUQUtxN%xbu%6xdJ6x07f zq5)9u|Hi4{tbsa{$63t1gp%iNkNoBPgaN^`g=J=q3q+_;Kp$wQC;H_@lb$nb{NP6!kL^5iIXq9fg zys*9f>%)nVgJeAG&6^b6ZkneilXkE7$M^+bUxlS`7#%iZh7S$Y78exXIIBe{NMM_Q zUsqvNAGtuib55!0nRX{43|ME%<%i%~GD&Ar*`))g*nfi`UsW|Ye^mUaq{P7G zYDykY(+5YDv#-{TVIy?vYEdPx|7rPxf})l16$Az;+XAw4{hNJ_8ADT}TVgZPhsDL& z;*y$zGRFm}hfB0G^71LgOCJ7J_Sd+^M<#Rfx~fJ<3RHVu@UUGE2g`yw0E7#-g{-iT z8NK0B;ew)o;NTP%@1wh~f&J=3OXEz2WkJT6LT`!t4kYdVgCOIZ=f8gA-nkc^PBt2y z8}4!rZmxB8)ihxNyw?9-NnhnUOA61Yws}f3xW=r%j@H8E9F8Lvqo0*<<8o)7@Gi<> zVMvm@X`H-+?$6|_pk~3U`g#-5uAjMqXw2icb=HpVyh!QIc((zl^M(9Ae%V!2R*>Ra zeeRJpJ;_aD8XAy;11?$!_;~F#muIkduczb78kVf}@qB}U`2`B42cUn*SMm)|V<>JO zK?>ZFS~7@jMJlme%%94)@B{gM{K7t?H&8sui!Cj4oel@+Rq*NnuiPT(1;YRh_sz*F zr$N%9WS<20uy~T?pbq0L9^H@ka_IZL-{|EZCptLspy;tGN-VukQWpUE?FAVUAyU4+ zN&N5&iVdpjjKc;8qas15kfZ?H$<`3~f_^1wctZ_7x?TPRzwrNkCe^|x&|$hP@$=U& zf!d67uY(`!M%3rXc~4T02a8?LO{G4K711bUD!{*+n@4#41&Ebb>T-u{XupLge9c0A z_XY%=4-Ci<2|@qnT0&~c)~tB&GV1&& zBK=@f6lp@C-R@3MC>jZAbgwVA#=yJV!kaR2f7`u^lpBGtzOC+^S~=t{c<@f>HBikujJ5`>VDC4osAPk&VZqyx|!N9 zX;}*Lq)6qGlF3siPa3{}{YZriq~1$pXikT|6iy1q;luQ$m?vtM&+A?`?hH|k_Ezbq zW5skVu$Awh4f@rK*grKOLzc$?TTa?E$&Qn$Sn}aZF^Rs8C;dL(erqlVX8W^f7l4}N z?0b;||HQH_%^j~;c8sYg+HFfq(fxpz*aj_G-bJSf#6H5x!F29iQ z4x`x3WK}N-|%p2W8YhEMH(N z7lM5nUw6NaW>4Bvy{At74>1jA2U+GM!AxqigT0%GbKUCUJyJyUS{p9ZqacB3awMOx zM zeRpbJP3eqv#PC11{GC67eS8!cU|{RiZBC4e;tq+Zs6-Os?+p0BsYLCVMe%wV?=4l0 zvp5gEny&|z{$H*KoJ!W=z{cV{MvO2Dk_x7e{g%&A*?~OSV}*~d(O+R@QXWz@{Y6%@Ymnuzw!%*neW}(iV#F)d1VFn z;7L(ty;P+w=_7`}(&UAx!NZo751ZB-(ldNzW49LGdpd;DEcr%opc&9pntXKo<_&kb zA0hYTSkFHE#RCDM-&|{W7;Nr)Z3YRh(Wnu<6|z*8LGYGw-9jBaAe){GBiRT+`lM%W zXTRi>V?ce$rg^zYR6x{N;mvN-n8X`IJpabsu<3y)fU8CS>=;W%^K|2+iF!Xzmxo_# z)nW5PNWhj5#93a`kh!Aj9fxB*TrI^A5C6fL{6gv5;=XOE(Plp7FGx-NeSCfP5*xMAuOdo>(+ZVdiAdU|MC6wL(IRGI#P(X1#9^q@i$`yLNf{3s59;xq?ns8KD zN@zNo<@uo|>iQu>i}pvZSh6cf#)c#^t_ zVuDe;2NfwrgBI6Mal}|#D;?s*R&yLZh^Ac<4u=4TUz22NC^CQ$*80DqSo*8eJOHnh zwP}S0fUaTKBQ)aR@@hZe&0MqjDHHqo^u6p`)8jX+%WTt-g)Y+d1KU`-bTJ9`5)tHh z68&1RwxyJuY8jajJ~v_5kmR|+$u%<^lP5byQLS2(Dw7oyc`@Ac$Hd(?0 z9%&=x;&1>Z>7&RJK&x0v+9+KOg(O)d#f8!5C;f(b&mjTqLk(1xhkOIc9qgz2XU)a8 zwHFUBn|sEKf~MBccvZmjqWHYO${fC+sxG{ROgCR-mt8PvB6(6LB@-rpSxA>_#+v&xAI5$IR&+ z3kTJi50x%2K4o^4m&8Z^52e4ivuafC>~ZT`%qF4;iI{5*8CqJ@x%m9)-X%k8tEP>Y z`N9DyUxD*fD{?gclB@Eph5?yk5-Lk__nH-e!3yVQMbFx4`&-Pp%BuUU&YTS26WaTF z-~S)XmV9&%yYR_foAw=lX#MI5t5S^VVqM3|1q7nf`Jx0Zh)BPYUEIusEh1^Mflx?3haI5PF z>wUXY+EPB8H8kt`l$hxBAya%|LshQTI<_V#oYRJf`@RWp)f;SFi;ZrA&37%v+t_)+ zhC=oSp`2|%pRqp(9T~w2g)+TYwko_ z`gGe^fm$Sq(y_onF>uY!d6%pw36LWEuz(cP^p5*u9c}<1o(0uZp#{t+zyThLS3O!t-?svXPZ3-MVcVR!cCdvskPdoV-becko4YuN62OH;6rW=^QUR`TS;%I7!KQ zVty0RB_Zgcm_R~8Xp_9XMEQv9mo6VxAIchX!ItDCbHCY?;a-(tGJEiE8~MT~)h|r^ zS)gmZ$-Xgu533iuYDLEuG|x5H6!0h{frr);)F0e0*lw+QDc z7kZImIwT*`#X2A-u{)C!N#y`Y3=PydaEhhC9rqE;PEwOk_{~lt@{rld-oDN&RH-;? zb-2Jc`1MI2INWxh^K6 zsCo8qer4H`^E*a+j#7Fr_9?~MA5`eXvk1=5$t1XlsF3!QU1r#Va6&{t+HO3@ZorTI zCTS?uYRe)bxHHEtWIBD(_DTcabpF|ER-L$89%41`3k%VCDA|fK&^m<1{B?5c=T3%?8W_YeN6lt{?X!`lh zTLPzkaP;`LmgRHC@?9mT?3Sh{TqG%;&5;KkF|jyP%9t1j>x7AT>{o9B=kj{t0%3is zus%=9Iu>%$1)T}~Gc{RAfoXG?i~|H}SjvW!@E2kc!O|trRt~%$lqPX>axU3ew3Shj zkx`^(tL8$@&Mt9{IhH!&JsM+I{Dgj-nYt$ikg$*=**Xj4Wuyo!mJxLVsI6n9aH}-$ z9gzVFmyNj2?CNZosBEh0aCYr|@PU1wf16=Kl#JGwYQOH6=URd6`5BSLK=y#x{12E3 z0XE_k<$wqQd_~Twa*B#lc`4*LDU4$zE>i;YGyjk;{?Twgb$d3i#gZ`PX~K1A{lg0g zQ0381{XlvX4dXn27((_Uyh@?0>hYBWy#t#DUb`88SttC7uNR9jQq1p&f^vCC{=>pQ z*}BcdnkO=`KPy?{X3APk7E-WCkpj+H8+Tf-=-+gubxYSe*J#e7_B3IO=IJLjFHXGu z_HeNwN>>mso;HUx3Ez7d5d}n1O(BW5|*m94E zYeM_)80LWr8o;Ws1F6!2lAI?TDL`=$08&6?Qe;!;4-OzP+HfpEw^jJv#b2?Q6vswK zg?HTUg<4L@_!SKbbPkBOIhWU(=2fMDNW`>j+`;PLWhrQLd_G+NeYz+T1cGE8DGc-KTQxY35tXP}TAD5F=b4J#Hy=#3nqO>tFokIXosFDv5R?J3NJWH=PE?riy(myyqa$i*a6$+-TH&q1vD#LDeN!noH z!X=p-?UgHrpLpoZJ-hC_#A~&toY(SrS;zjab1teXA0yed0GKY-)TTXDK(gzkniRW? z>?6NihLtCZGpT4Z9A?sM5Y)0#y+m3Gm$?182(>eR%y#R-xd}@z2DioUR zA?JtAamOr+MYBoyTROIC--;zGPanMGD(`UZ==vLSa}!yJJ}Mz5?Z!Wz*14mkW?pwG z;wlRfH<#-8BJLCD_e}Efs6oB7KJ=%=-!|+d4u)S&P{X``Yqe?6HjDU#lqH+jq$l%f zy3~~Vu_dV)k#UjsPS_HMq-oRO)S_G+t0cZan2xhqa*ADMSDH}1=egBgf*dLK8+Gs8a#ywq+f}$ncr`cP#G%)A1XCn14R9M-=3Rq) zkZ^G=i>M;>$<|Uiqo1dkjL2XiiwLzvLIFg&xQh`paT#UZyIGoKrdFGbyV0lB8TeL% z!7+PwqQNlq@A!DM)ihY6J4$W((TQzSO ze$XfygggVQ!aF8-;r~qN!b2aqM;gxLQJxWW9gk;4d$q??jeqG|z++();C38SJTJ>` zVCpB6EGO~`#8@_jiAZX4hB`%APU$`8O5#KwAXP}!Zns$T>g&ukE}bT9w(za3ZO*K; zGjk#lXB#g3qwuPRGkLezMOjiIecbmHMkw3AK@nK^cZC~(rw4}^Jq9JG*65h+ldDE5 z7uKvXoAYnD`8AK{H7e8>EZThcDT2f`a3bSlivKE_ej+{Xq)e4$aZkF6kpUObWNpcbP~q;fPlP2{2A&6`oGY!txHPZ}EG4dZZqOQ^+18O|w&Kly za2WIoO?X5@W{x=}Ax5EAa}h0Q>-UOlV@KQ2g2z}TKI-AAGXD#>4@E?kb8vCe_xV1< z$e9VLFuQf_sb&-0>yS80Hv5#`U`U*3TdoOKd(^=SQ&kfgR}_ClPLI6iz$0hU*MTuU z2^~Y4nJ81CtVO_-`&m-BzOST_!VlN;{POWP6TaTMdGx=wH^+rn)og6DUqJ`BRUUG+ zdU&C+edUmM^nd^D>m6Fz?yapQSPa*fi@Mhj6K2HK%^bE-6#6eb2Y#UN(Xz!>fH9nz!{WkUeo1S^*o!=0cuU=vkc*ElWtxn&!`Pu|$?C8T;;tU(KNHMnyS=Ee$ef$4kBH#chK!uT zm&BShqH#YHwU|Gzb={J6D^>~*aM$5`4;`Y#d?h!*jxrqGREr}b>TgWJA3e$oUlh-- zhMW=C<|tx%RO`_~yt9D0N(_VP-liBSfe|)?*Ez}Sb&8oQ#tTzOqNnUcVyrPGUCZb4 z5uEzV@0H5=`e=RRM!g}qwlNv)74LUEmG+SOrK{BJ^B`!C6mAhd$eMh2W0(X9xReV+ zy{XZ{Cz6XftlN!h7fSdIadC-v-@JLtfls`eqd*%;mR>35c9K4u{i~~oo}DRF9tm9$ z$hMi8>m@-q=xZzItct!Ft*&2>%kD@Xn77)T2-KSj>`GErIqV7 z;L{e(o_F!MH#jTvjm7vurt8wBIAc3p+D&c-C-&)>YB%c%-4}Nk>nGE8+q5A2)pz#G1tQ!Uv|KIuBmM zF4{S~ZPk|ZUVrVvEiK!IcX?lVMOvSmU~fpiS1z&h19qJM)KE_2)cWAWKzL2o$cJ>F zDkC6LC7dC$T@|<<*|Z+49pdN$p#)5g`vzUPLvfahHR&r$Y8MaW=EaBfM4V&=xI!LG zHZ=RX1Fap8P3)|lzqr14;p(G%>Q^u8evo9pbl<5)DD^87k?lac8G=Dt1NkgbQY|4f zGMI*0`I9gK^PIq>rT0i{@{PC#8Pc+d&n%pe(idzl((BRW;7`3xOr_$?@mcd+W@m1K z*vGs?VUC}-wa#i)RI28W^uk`2&^C{}VDbUu4)FiFzJGLZyl=zJjCXskbies*mKn@i z4J<~McI6%X1-%i_1)SfScke7fzq*)=cOjuhn#P1|tOBm|m=2x|5! zG*;e`q1uJz8(a76Yui|}rupI*UPzO!y#})VD)2F=%2X=yYlHMB^nzrQLE;l4BjQ!d zOi0Lb_->nn+}vthqoC!-xKr_eqb@Tk@${{mj6IHBZx!4sd7|pYQY>t*LHI0C1R}z7SZ9J3BQ#lgQF6X zs+kxMuI|})E3#22oWFq2S=OB0Yq#a>K0U`~ccT%E*_1xJ zMu@(gRmI7QTD_@Wur74E@&tReMpl46Ogru)+~#u1IER3v7q!~a4Ny=W2T#;G9) zN{k_p>+O$|Q0A@F-^JzpPOHs}h{%D;$MqbNbH12T!Q*+M=Ta+V?%+(I=1oDUg0fXo4hqkrvv`O2BkL( zk1{0>DhYZnBJHwPdeQeFMmTcF8W9ZqqJ3!;WldeG>&c9d4^gW_;z5wQ`k=L9IR9TA zH>_{%8tdx1lvPTfrHk+&K@4YsVxJd-=V21{gB_Jez)m?)L%;~1l$~OnD3^j`@=?bN z&>}_AF@E+!Xvlv=ObGgbHSLmiGmGi#+uriXWsRpdUY79Oym`;F_RS0V_;eKs*7WnZ@xTab4!%&eof6ZXMrs;RU;e%c6~gOUwF=)~Y6NW_k_+ z?R2iov98p6oNTKW)Fu$)F}VBFLKM?FKKVg_7tuO$LF-w^ZrPT! z;JmZ`wDZ69Xf5UzJ`p}`SF(`RDuu$zP-FtFeXEh!L-&OGE$)Y`rhbd1V&(=8F5Gq; zPf-X@>3T^hL!jwpG#vtPT7G@16p!-TVToM)z`8UZS)rPU)Ge9XFgJIa+Y(>Yb85NU z{XJU$@cdDpk9e$9($BYJ5a4Oe=E&JnbYc_TD4Y%CEGT>jf8QpA)M8>l5ob&e+Zv}=frUIQ?zJswngfYTp3aqAtZB~1?odNoSmm5hU1Z|FHBr_1 zye=|t+!+?CR8(3v;o@*u9=r!e0n&`0Mxga@s%L z*E6tl$Hsv>zHuw!Iy6FRCml9IUTH)8XL zC289$9m5P>Ylh<7t~G>fmj}-)E~-*`?NMtCCBhF?!sW3goq63=RkD*KN$cre4%*t? z9*)Kaim<)mm(A9MhH7RjQiXFm`}dq9e5UVOzfQ*u`)<;5p9}wJCEtx=xewtyk}Wnp z$7Uu)DGCxesUj*un{0BU!K1lAn#isu2H~H>^QeW(r5BxV-#>S_YPaM3qU%*J-KM&4 z_~D1&p1OXrAu)}~bzz~GZ`m?%0L=z74)$!>a=B;1GdhYVtO4nS^MT=fC&~`W@t*E$ zPRhT)*k&??oN$y7STcsnlz5W$(=a_TxA{!z{_m3}fy*bK#o#{R#^V9v5uw7$hD3Yr z(jjxt{46GHRdbNTe(jBM`b4_nFBm{+;rW+&{uSSjOuJ2~is_F9MV4DBb6P5aLlUQ( z_EagV_O|^F9Vd%)csD6ju}(DaG4oI=jV56TRLdo>CcCH@457tE@KxcZZjDEe<*0ah zKs+!O^SmafK511&wKs8{t4`IeNXq1%2>T38wOEL7ZbhWUT3It;ZOnISGgQ&q6pbr5 z#H&!%CK~kTW))}+ipXG95WbRIC4IH;I*hA%PJ!T=k6*H|>QG(ly84lY%kN|nPY>^U zb8TE~n(){2(;~W~GbfgBtX-0ziqG%8cy+<*ytM{>((7wZy=L>Ks4yshu-)fje_k#7 zddWb>sORGEiIf13r4FD-)rdosOKi9)dJzyqaK-)QjAa(|ZfVi6;^5wL!^;pgtR01o z#!#d6^A-Gp1yIAHLE}Gh)3$8sx%@wN@kfNq(co3-W)H!TuW@_N-Oc}Sgx^K7qXO94 zOYrX$!1fX;mrw~jG~mT{szb7=5pa7tWBKEX4Ah-c(?{;+8WfgdUc(!99BD z-7b{Y$33U`?KSegq~cA?mmT1zq4T)^o<|bkA)Px>GeeC{yYD3PaAe5zz-5V! z49|_NtmFB*(M@jmCVKk!O1`cRH~v?Tm~bZlx2^mk;o4VUb+g;tot+3h`Z8~+Vj z)seiQS_@A=AJO?DE*@g)Ga>~6bMW_4R!UR|$^9WI7b(_&BJw^neAseh`Zu& zYPIQFEm!UAcM56!&VI%xSPSZ^%w;8+icd6OhCHi#`QEcvdfx@}0V>Ze)$Z{TGz_t) z!6EscXW7r;=j>05Z79eU=>QZtfSYisIFcCUdp4O5lNqtA#xQjY-G2(-TzFFi$jgE zb`9e!*YhtH70q!Dj@)p=$Y5blMO76tSrD6Ec>E#7BXX>ldO5hr!adna$r&S8z*YvPFG#i{h0@9BMvQl|R{46MzwjL;<@IO}t#x3xt#sy>TR+iQT@i zQkjg@60Uc%bYP%{5UEBk4$qzwLLs33dF@b}f%)y{+z5!nzQ~VxGjQBims9@khnJ#&mvuhs*`D zpGbycvKbBuv~46%Hu+Cf2Tl6d#Pg-O$bJW=1S^z_UxL0AO~QdDG!SeapyxRwbLgdk z6C^<3Z07Jk2_K4yYBrF-n()i?9Z#hT`0s>In;6O7O7wgsP7T>NYfuL*aD`^Fa7bHD z^O31W29D5F>L0KM5qy{)B@-nyQgvyTv;mF&g%hnINH{`I4*b}N0s$edb4$(6f&};z zO#6LwOfVtsSmQlvm3GH=3mdXpGj6;&!)oPu>qKmCNZoRtpYIohczK!Umk4C6M!9L{7Xi%0KACK-hTzq^gyPMYzoVF)N=;9%8vCdRy zf^0xqXT*L^wuh;+Eo=#xAVdj6*oSJ}q2z?Ltg-bxbrqGAK&}J&)Uu>&L#&qAd$_yPyhXUTsIez? zbs?^y#E&3^O>WX4iEe@cpi*(I#NAgMAdUX5bmwUhKH?${f}P9@e;d_W!;)Hy)*18{ zs;`V6(QNIq)fN|)Z{0d~*4%jM6Wn@f^gXU+$m@kZ-2n_>1;i)|dSIW}gDSiN(r(|fGxd6f=JZ+3oRbw)yZ zQeGSHS{$jHBh9NGy7n1`15%OdCFF~BB1O#MgvAm$$kQxzaZ=E*<4PYN)uMh>3QvsW4;z_HLTugSOZ8nND#OhY){J3gw?UjRGVrv!GEskN;qSoIleR} z*PND;&R?K<)vb%EHuT1EJCcoE28Ry{9Jrgjh|}UkKR$nWXz=`2R&FjD?AW&RI^oj- zvjveaQenyL9}u3(+VX|HcS-XmpM-39DQ4oD$uqmX+J-@NRcid`@)NRa79kGq_j-(T$ ze-oQLUyJ^?NKmZ^3(-RzZ4I%F@fh@0tx21u7%=1XMl3)Bk>BX=l6Giuwu_h4F zd9{Am;$^P!j9G*24!eyun|>f{zoma+xfNlwwt97(y(^MS4p3^ac^FfWo0I1 zHl0jbiji`1^dWiBHA_CxmrFbNOlbo`SQ7645wrOIPj%7QTW_IH^X3sSo~Lb6ymxXb z*?NzMEgSI&4L`E`+_t=pdAPO(sLen%C`eXj{%Tw-a4!a|1x^`R=`tcb<=D%`SL9$b zt~3`{x|!$SM#?CPj-eT#+w+S0t<7(~IprhaY)x&evHA>zeq3@&ve{yb=c2;+@*o46 zAZ$sX&nEbGf?4bh=d$s2e+~zwjHN#dwiwm zj5Bc8gtvrulHqbA#^;ek?4-a& z!7IlHQNGnFuQZvOxD;959Bq3g%QT#Ih{c-Cv1|ZGvvyvZIX*eZHa9;#BqMLaQERg( zPf>Y>FUW?eh7XETRWv*UH!P?3e6nEK?UyWzri`lSQ*~$1`BbZwadAl+Tf%ToE*toC z-^JrQR!$5)>bc>Dz1SO+C-Sj7bC555_k4 zweMaPd&!{fPV3(-{7)6_OdL|{k`0lX?ubSI6ki_xP8I7+9F$43(k?w>vS;{d&G@iRIdZAIAb&uepEr8 zb)(giv37N)#d?qW(F@ftuPkaTZ`#JdeIlj095;<*)QuRSRCPdMzT&x2cV9iIai~A; zyW()TDipn)6{f`!!w?k>Oy<-Z!aKA>p5TzkHdAnqp_A4;1Tn3PCT)TiXt05x@srCcnCsaQRi(9LMS$4Oa^$gOamf!l+bO}{+@%qKwll6wFh+v}tf{dfM?c08%xTB? zu;t<5%ZJ^FDRAMx;k!koCl54i99}q(R#aFW85Q5_QslQ3ubn-6Z85%i1^$D4)a}bn zs!}QR4Iz;#orcx2EGXB9^YJ9z$4bOGZ^YS=cp>?~fk@*2CO1Sml zC~SW1KKp1vjV0aI6w%1$P)upL^l{&f7$*M&%gAS$ zbFy&Wbk7{z-?4f1w#>}D+>wo_Qb7C1cKjX@XHf>J(WS}`@2M&%NG}RJMT{}^Hy9;; z#2+2HoS||b8RQF!6LyB!=B7-fmUKP;JO``Ghgt%lB(wKXi?Hl#>-!xopI8qzRI`w= zke#f)I4a7Jy|qNRr?AVi+OkE-^jpuovWNXxwV_H_FLg8Y^1%5zc{Z^*WiX?!D``NR zmr+=?;0Z1%Id}b*<+Xjyd+oH2QcTI8$MidpJkeeun}vP@Hz77{MI)0l0#S|M9%x)4L9#c67pa&uK#!dr{Tx zM#$ZeAOp_JvkK*;u9;8TmHbO;FX5U25e|%i!A24@NWozYu$SSXNq8~7#gAku?j4xh z`&EBs|6AC=a^+tdev~$Z@FhR8iB-l6gS)&@bG4CaJDibxOmxzh&CSmvxV)D?<-HeQ zJRR@Hr~fAnZ}B`;$@=OA_ma?|sQrEKn+|l2cMYAk;l*!(jyHV_2;!KS6Bi=JC;j?V zs>C=KE0kIkqeR}nVhRYF0n39e=szEFHx9z*$fN+i`pgF(`2O(WhyRf_6=IgyS^%s_ zcjhcQXt{Jl-<6hwOS(-1#zwbsAnvp`eE(Aad6bQIk9s-paJO&}@1K#kV<;}`tysRi zqPL`U^yui)Wg|z~+g>jcFDQO_kK%dkHOZ!%O?`1srYUEtG&8M(;R*46`H<|zeZ~c5 zX%Wzh;N&QmaJO{#;cCKRE_g)Hsh@gR{pqH^{cTFFV~wWAlmCcUu|#uU`kd;-9z#|}Zknwx+m&5wOq-RE)vR_cP)8vv5cg&Q zdgxt*dJuGPq~5dGztXBhz~e56@c%S!1mb97q?)6Eh7`=eI|H0^my<-|By&gJwx|5W zzr+7TPiLWMj&QfzJ=X|3MBJz19h$zdg*^yOPfWbijdC*|xP6y_z}M!6_buWG7omTU zA4jP5U#}z^X1rp#i#7sE;t7N56}6YRaF8UTtzINvH7-X6{^iSFl6m^Jwk?m@SlRHn z>!Lpk|43f2&1xFW8S1^#HoE*SwP8WVS?Ukqg@_Z8j_dT9IKO*hbm_+Z|JuPG^Q@Sg zU4HiHK+~|(?WtMf>A-vxL9@|Y>~VQMkdUsE;0pnABQceea_S^+qex70AcF$EAw>!& zTuf+-v!u%!!GNs-iy{F#aWK2bzQ4TcsQt7oceL&N=k^GHxpnKh;SEoj3O&}+6`n#< zOzNGp>Myt;EN>SJe z62b&0>BXdjrL=G49*7_;hn(KS&3L>_nalP3^VAk9CS1c?%oY5xl914b4c3iS8D_+1 zRbk1SQ^VEG>=MnWy306_ph8RAOZ0}MsDaqMZY0{e5ApmMtdbyfRo|3l$qEgLo2Wbo z^qtC~s^*(n82{0Y3p@HY^fX<8(0WhL@)79L`})`J`#W)5DNaqgJB&Jy1Tx}qp^*Gi z++4&{5tO1(6gDLw0dZ?ef56_vnmUay^1KmC)|3309Wld6yo)HDLvH{|28Cid;%qQxy$BW*|9Hf?HCD|dg%_2H%*A;=2zBwSDGOOpYe;0ii{y(;!7 zq0k!k83_p4>Z3@m1@>Sj_8{yJk-wqDQb~*Gw`dR*kYn6Z36SSSkdAv9bDoII-RO#3 zpjpMO(6p6mm>KuLb;WWHfv)-Jj#0t-a&%MZ9)Wzzb%TZ;6w8qzxkds;9@%~Pc29kl zcg%yYJmAf$=U-XRkF7xqQycrRNVp0g3jp;l8oT8S%vphc%ol>!9*_N45;O-^NVlz8`l=qiSg%k7V-Cb`cL7C)l@d4; z7fukuep7gT0IN!Am=>D@D?thb;8i1XLggb^U}Qkq*kPV8r&6Q7?^Ee^re^WuC_jd9 zn)~n4R{gvCVIDy9W}Zbc_vf^2KX2wP58?-SJUp!NzD8y+q~4cmF!+G?{E6qkbz_IJ zxDgyeT;G>=giuqE5i=zeBr`d?aQfIg+3bp;Rnw%ou}H zOum*BINcC=Ut%w#=rsIsV<-v_5pR9BL==??#;CbN`r%*`5+MtjKDsuUXH_sgS(Ik6 z^hTjY8XGUta0{>KE^JNVlfqQ$AR-rcO8?}$7-K6qJUDY-@Kqt4!C?LQ?70i)F1dl{ z-+7jQE{e^<+Xa>McJ>3tzV`OMzP?ue!_;!Lu%wy*H;Zv!KUOu1co$0OGZ*xE^3;T8 zwo;~9o}v^L=ghWkm}q-xtZTckUHY%jlm_^oz#X_#PxU>m8Ct1n9~m6!>>L?f`W1fG z(!`ca@AKV=5x&pQa1es|r#zl_Jh$P;^UgcS#Uo7kT(KV=*;9$g&4OI{ZG<3ML<30h zi%0M6hc#icx=EfOI-I)Su8l4Qaj?f2}n`^qtX5V#rIpY`X zJ@mBo){(J-O4E_~9b@rZ23HqU#?@pOXg7!GUbX6UkxOq}qcRj&UeTNVG*ly6dyySJ z8OfQGUu=)PP*Iv;-oK(HzSmftm=ka4&g;(9cj;XEnuO>sYeufi;)sm2TH^ODG2EAq z(?MsIawP7atQV5|`#CeBtQF%?2u6`NG^t0X43H@dmoKiOsMtlNPdNYCNPEP(?WZA{oS+;y zK@WkcB|I%jZZ-?7AmOTrSG3w!BA8vcJ=9iYUsal#J(ig>YpuPY-aRL0Iaoo_$=qTH zCu+xbD>gwV&xK=x-}0?kKxl@qrQv0Ys7hTn+r`O-_Tb>abYT1&Yk1Wr1^rU3=zti5_?)A1e z=jJ~0Kz@Gv;pWEimn9tdFxEI;T;n2I-;-I|r2l1mlS0Wwktw(vE-CjqB{ejJM618N zU!f}*_A4f943VpM?3V-vwy6#0@~u6GwnP}>7NsX@LfO1~&e-3Q(3UXZEDX*H3myMH zHZycKn|E4BWmS1-kX~=+8f9s7r(LfHsP!M6P5|wCQMw}XZ_l)C>O6hHirLq(sM;*w zcSk&Xd-m-8PKhj+tCLc)N7wf0fYt7+QaRQ}G}buEP6gkJ?7B-7H^CN_Vy~`W%^D4i zzLK_t&iI98P6iK@jYtr{zvBcDD*(&va?YqpmX!CG<8g+@s=>qbWE11o)Yg~_%My9v zBYuQ6HN}qphVocdUd}Y- zk73OJl`|ZedpSeytF*%6$(=-IX2`#k@?p#Uv#b+x$GqyCZ-emkRfn@Ct9GC-Y$a z!laV$z5k2R^H79rnVo7i+P7+Ao?vJCzSL?zL<#k2-fy7(u+J|VTefEHflEqr(nspL zL#ozuVboH{kR&{wXfP(^mG!dOtN5E1@caVkjJc@icn{{vi`gcGrO_#)+*r{+LXWQ# z^gdQ8aL-O@4AIJg@07>|!(SO*k%eGC)Vo8^l&Meo=hKTUogm=l0=Y=G){As?`HKihtyoj zq5e`XN!ZGEh`*hFo2&IiYb?_XXwd8RUBKZt?JLT4dH}sL;8Wpl7mJoOWP89J#z7|) z7kG!UZ^XSNo({ierWYj&80k0hpNKY8VC2I4^LvH6$%2kf+2BUz1=%SvxaEWDSZjU8 zhV;Uoq*K*KG#bD$b zd=DlYjUC8+g9(O)2DgmXu3EmgZ_ggjwj+OOz469YG)A>sJl6Qil%%wH&#YA}#RCVN z_SVIS14_L?YKS#-(o-};#vTl9(FT*aMYLS0(XL`(6agqjDuRNd;DAby^xrv>DxMzo zYBPjs^gXe0=>-Mpak2Afm2Hb(nV2}B=5lXMud%18a#P1^mCB9h#P3-b8+(=Rs7^Hz zu4bX1=_9ie_hLrWilh!-Oq=pPv?ReT3D#lBQ!sAk=|q&O4eq?{=(>1A8b zyy}d8TY6fR2^|u$V#4D`V^nb~LPB%U#wQ_X9^W2OV#&0{v>S7+aR~?v(;lI|m{gaG zWDj{;czMRVHcg^Vrn`za#h>E{cG09MNB*@Wmk6eE8jnacXqmAtRvX9_s|_sCMn!4+ zSwr>whVkkp?(;@pQoS+yZ((id#?Q=V)hGfgkX2JeIg&in#Oo)!bPR+J?J9k1oh~da zaseA1iq08G7Hl7WNB}W=CLu4~x}m%yG;6`}aVd|d{`ev2$|P~l)LZHIU~-~8t`|AP zRE!&lmdG#$l0VIY$2p;$0xSO_N!p$+Nh4V9i8vJ2Y~!nj_`4VKBTIzy7mx6DBlQPz zs%AAEDz9^xxs-b#Q?W243A26D0759mICIgCBT{Q(8&=J!OpA>* zW<_PE45!AQ?!I#3WzAa?-?G%F29}Imw7gu0P@#d~mTE(7nil8}I*H=Z1Bp+E@)8-_yAI1GAG9xEpDmtGs?sxCw( z@Fa8qeSmfKOZ`tJV(VqREu}RRRT-nB9dDxrS9D%k{K7gognagrEvb7ON8J1Ee1)7# zgGkotb=Pa&8~J)_mfRfPu_(VHy(iWbn_Era5bZci$0>B(x=@840ZLvN*ys3&GNP`w4Hs5-N>LZ1m3t_1rK;}u(1MA30( z(W}6T1)b7x2tUdgtOzg5vEFTTYK_VHs*q61^<+m<^2X!l#YT^OJ2GOoHq~sk-O%23 zm35%0CbeAi!RUt%KbBBAhm0hsFl-8pbY_hm=&vbjvGIuoEzSvhPR8;%!XCDT)eqF0 z4TDL}V*6l}t!lJ;`SR|Df&!As9M<6%<85%^D)gbxi57c^>n zL8CZV6+EN}*_)`3>eF_GsH5WV>IkAKhQLVwiNA8Bedlw&h|KHkS;Zq`UW0RfR&M&( zPDz3JlktVQ=l+fFCf;dYaZ$d}khQEN2L%ePCMC(nB-HegLcbwD8dWEM31s(>iv?9WM&|-f-`5$4BYQ zgl0afBXwVbWc`vR#)iC4lEqSTVc5eDd%P*Sk+Folb?ZD{4`b#r>p7!#4X_58G_Dbh(}5)))h)2F8oza zsvMN^AhUU%0Ng|W4r48~8){r<%;PjV)ly6OY_mSPtjHLvFrb0O0B`IpQ+I|4K3=`b z*y(v&qje^GG|J+bH3nC>m{&O<=T)AAajif05H(QNclD>AvVYVG&k3J)Xq6nN>Qr%D zu+sVyi`JHw-gcz+&63LJuj6~IUT*&CQ15|Dt~m{BOnT{J#oc1B3(7~VrcCW$)Ekq4 z+e8Jq=klK?8HS{NiS$j8BC^c@W+S1|NLDqCTv>g+aCd!uu~z$mR{Mok`**+y8{%z6 zBd3^zSL^H9()!BUd66K;u2d-o7mxWyRElP9Q&4^F!iBZwL7cQv@Rx6M zreYJV0Wk-WNX-3ozffx`1UeHpW;5!m_<~ehe{$BgOE&TCx=*v?k+l%&Q7Jf3k#0vf zsk2%;S`Do#Wsk}H@V#mJ)}}SC%JXPHRKx$3I2N=YQpp}8Nj({N!urn?^(D^H^d8aE zE%#}F6}YHvt}b7aWK-;v1|Z>pZm!E(o66Rj&Dm*%=CovUSz*y;J1R$?t@-RO%_AcZ zKl~3iD16^a7Rd_fW4;?O0{d1%?ER0|{qz&d7uIHjdBthG8im5lws&6Y>0EGXfxCL3 z)8qEEwc!c)KkfUA0^~srGRQI4lSzb0{w6%fUoFp8!P$+;RKOAkM&?^g&nkX9af3tn zsV||IIfM@fAP6@N4kyJ#C)o09md4do<_RA%n^y3_)%P3RWO+`5in$A}#gA|F4Q@V! z;1^%S#U=^G`St|RG(B2{BJZ-gx?9T7;8n`ueGq-de-LeK>X%Rl?{nGjm@5GmIM8C` zEdU;-WL{JcAq@c1A)J0nxAbRG%h|kJB>|7v4w5vuHOuFtPc=XPTi-33@1D@SFy6ox zvGzr*Vf=*`MwavQCo|e4H8*{xY+{*0p}u}Tzufm;d;6O91x?J=h(2?|b4?4{nHCA! z_u(sof>e<47o%qz&@It_BJ^en7^skQGi8be))k?IOHnmTpg)NM9C`;pH^}nhqC(`* z>yeNdir}iV7=|d6wowl?Cki|W2MUk}hAu^Vrl>Gr=dqF??ALy2yVBsVIh@`;6I>bL}}kp-WkGxw+Ijp|fQF_)yt zDboDcqK{%=TE|4&^D>&H|A0H85Q*=(dli}ZKk#4ihT}oC1*LP2amtkn7NJdV>ol2} zLi2X9JNwSYbbgQU?6wsu#u=+lag{Hh-_*6rUA$_~DtuD7TDZ&+ZDd@Ss56p8yFls# zO?et9`9aJ|(drQscO)SZ$S@1_TSdR}@9^NTq5O6}H3${!)4)o!knNz@Lnk4iG^CI3 z=kd4)ufB-=FAqK`9>}fo3g;K}d-g|P-ZN0KQFti&)-}uGh5I$EZS&ctaV}Q+giMy6 z;Q3`dze#%S5@|rF5qWRI1MTZdoDoA&&f;|sB@D%uI+wPqd)6AMM&`WZM<;7!V*V~T zWOB%FM)`>OVZS(w$#nrUHxz#jP%FSWU^Sq)uc&s&2=*JoID%wN9FP`wx(j!y`|^aG zAuHk;3Uf!O>b;LmrqsDjCbWF{o8}A7QFeR9BJ)-4Uuo{xt~oF|HnyQ*fxRBQiu`ZfH$6T4SO+(o0+CwXR#&I&VRDC-iI~;y8qJ zlTTMh-cMkk(V6l1-PWuQ@|=w7oJ7|^Nk5A6xkYn9tklq z0_;Yy%LV7>&4`X4SqfNQh)bo6=VM`uWJ0@aC%cSCni*?ur${)EQi^0eGccR08Ooq` z3bBiuTNinv!`L@MBiA@9A;yp%%Vz7E=BVp>D=Oxw{Y(?zElmsxQD&v23GwV>;V<`f zY#qIfS>qz3(t46O7JiPmU-+m#goUnAvY7X^o~XM$*IeVdmx7>eUUt+Zr41oXflK#Ng)V!e3Z)`~uO2BwaEK z`tlU2qkJ;!Ksl8z4mmfQEMZZj;KUoLU-)muFI^Iv?v(3E$h8VkPa+ky3KE&coays}a5uuEFNT$WFm1?Bze_@p$Y5-e0!%K@v7S(ty6Du{{4@kEaf@pXz}eLk>@njMrsC!>*!w zY$v-j0CN2to+7|GMpzD{K4FZ@mNs653IziO;trdHfO%|-nXL-2n24L(-@a6-@3MH{zbK|lXDMBh?MN%di%qN$Fz(?w`TQl7d@k9^$>8@;?J^!#axP@S(4-Em zAP1k8ovOM+6N=!ESR)%jOc4|Mg%2f88~TrAglJO~iOv(s_sI|@#sA=(!1kc~HmN0? ziX%ahy5xV>vYLO|^-+QV0AMgQ7(e15oUY~Jb+IMzw>o>w!nX|yMFXp$=YqDSRK-A3+gu%hFPr; zs}@F2Az=Z2sEnmBbyx4 zv28!B-@YL_QkVKcvkk32&5kMH5JlG|c$9!Z=w43UFudB+5otF;uEsjn&ND?08Bs2K zL!=|4EHY+&a(1Sxet40mda%9u@CA08RoGC}5UDT2eX&EoJf+aUiXtBqc|C;m64UgT z@{tHuC{lx{x5UJiG8Fu@Imw11>l^o#;+CQ*frI2SDo*p=tl_){gGGq2M4?rlg?(c& z=zagwuq_&muv5d%tlJQOXUo!xW$OLH@wj-X`iRUenL4eT`;V`R=UxzA*IPc~_AOoN zYqwIFEbDn>Mn_o(!@?Rgn$NA_?MY?DedTtuVNJ@aU~}%A#HUsc4fYJJe-KH{E&N%_ z6HIv&*r8b1c_P=J^r^`7KN<2Jz&aNFiYZ79a&ZPY?Qf!+l_}9vPLl@qw(F*Z2No~+ zb3#{HY3%cwzpn_>ZK_mnL=TM3FZ^8?_|Mu+ZSj$jd4fw5y*y=8Obe8!l=>#FbYN24 z4J&64&mG%bF>FCUV^2$*xo?#{!qehmuk7ESX0>|iU=0RgHHm}!fFet@2T?sLaF!5X zNWYb3eG$oeRPrMxCss^^zJCoimC-b;EjBpWN%*2EJcjnhM^ZRvRdvm|?br6i)s43_ z>^-ZeEv4E8lKXwQt)nCNIdKegYv zqUeOqN_)9^EuUllg;s5lQDSBQX4uNzqQpHbkM&w-6CFBq$a4$KUT#s-p#)0$j-7-1jm&R*vV9;FEm=hzR&bpvO*X zR)ur|==R8Gg4m=8i|qh(D4he0>_0dUzT+CU3Flz1hW!t5;=4vZ()>&KPQ#4vYCape z3uLLPyASU~ecuGO#8=HnL?mrajR?mZS=t1TkK=33k%mEyrU^`um~4C|J-sDqbyADP zf;VQbPU1Hj`6Q-p<$t)Tv$J!3&)S<dqKTn9+&yrh#O4XDRTY*wmEHekh*?> zro#&tZ$hH9p^A%~cqIlIR}ciLPQyR7+9VY5zuHmUX|m0-zWnkJzNvu{jtPpuym_2nx@pneS-bY`QOcr`n+Hz9SVOT>5^K%y_L`AYH;ZPNej7pfta}l3| zKHF36ds1_%c8z9Ob4bIb3ZH52s$paNcC8fCtV4W+HTv$u*HJthitT6qs6NW^SS@3< zk2#_Mto4yr>*N2kcCB}H!`ihlAGWUIMgBT*4v!$$)gjt~i>WSiz+6cg|ANl|?TlEL zECs~D@9PHmyV9~tIuJWqR&6q_L`N9w0bGefxK+)YZP$&DUYk6;GRJO9N$j<6*L;1O z=7AS9Z;ZV5+I{zZC)^kk_UUZ3GBWeIs`NJ^w9eF!Uk~OM#YYaC%gby7MtfSON#8%K zqcv-FMr&oNp-OaCXi<%F9>2A)3IT0#WmMl;|r$qO>y5}V60cSxysWvF4%FP zdbs(v!$MHx7&5}7dh}8E3 z;YY_{)Yd4>T=&!*OTH>zYiXG4Z9OZ*5^=T9a;bSoEL4 zghLMpvjgLxwrOaQmhxMYxoYX#e2-9&m}fq$uY!Nr<0{BCPn@!CQ~SjHJ`aIS^|3MW zg?X`s(MEezTroDoJ?HXQUB#bE_du$Xc{BWC!iG}8Z=kFXAY$ahU^3VR5O)Cx5s)y6 z>`5|mir$G00hHOV&@t6W7&|UvJn&3xY%LN*QJM!8kXchZJwXlg~*k5?MY_L?SEvk0_I<;6m2HWHB&QtG(63 z-vMZJnec&kd7=8LHy4B&I=JjPoI@=2L-SUMyf(^X4a2&(o}kIa^DByTzmO9(_KBt* zZjLC|0-tF4MZ}bz(AGk9=*a$iOCk~OcCL;tzP6eg+gO0_PI5%h1 zHkYp6zI}D6D?P2}UJyXYx@&^Pf{ycpxs_{8+7^>`b*3pNX0_JTqBX7UEiSIiX+lm6 zY<%MP_x@Rk5jFs@0fbV;P$aCG3KoOy&$C3A9El_l058%l zB)?b04}mKBO+FEO5Yj~gbOfSn$hc5M^SaEGY`21+$Foafni%RCD}yy5VVdAz zxA4Iq=7)v1hg~{2XvlUNJSE+ymNf@2YmQ{$;kL0s`B7YR(=9DcRigF;ts&b~V5lBg4D#vz0f;st6>NI3nDl)*<}b(Q0QO zHVNaB7?ktaFRNTu-LkM?%g??YJ3bm3dTl5rCCp4QwkpKft`pb82HeXDWV8hl_KE2; z3&1ueb-T#?@tZXM>Krs>snDsU%^~?jt8tQNfmKDt#>xFnfk&zr6Ac5xUy|SUVrdF|>Zuix7)6b&=keQa4c*UvUgGa zR}to0#MSxwccYA^@CUZe9-*B-zg&0=0vVyZd7y;`E7YIyx6FEM<%JU?BOUKDWeuy{ zRog-hS|K)YKZzH29rOm-50o!o1FvBjs*_HkUz2w*P=8Pqo+uZAu47Jz`sH1Fh%+!*pSK=uMkIQK>oe=U!%VkT^l$b^0e18cP```SlJRXw?OuvXqUjkmj zC(<|2AT+s;yYcYBMm-DWMFCrH=lLyQSSR3olp}xZe(WU)V_zc9`Xq6*8SDngLpexD zI|HI%a<^dfkco)?HG~rYLpnp+<9!&Qzf+|U%Lj3HMdfF?t(R8MI%+)6*?S{)o-TO?Vc|7U1+xk5qr+sVa54dRqoZm-V*o@&;8p zEcc+h>YNRGyZZVz-2ZQP-G51lVC_RhogwALQm@QX$cPg%V`n@^fPioc;70_Q4uOmV zf<2)M3yznmn-WS62aSQ%l zdD`-*urRZr2sPI__G$h;F#sC4B=0@sGX!b^(|&dr*(Ab)`EhZSRYl z%u^?}Vm}Tn*Ta~QO#KWkvJoPaome(2rdwl;H6@qo zjO~RXO0%h*#kIFDTeeI%oE@SIwdixX^7eLU1=x?3$C1x~`s5@B=HXr}LQG+X+>;Zm zNIyp6$3rGZnVBRh7&*Y^548Eq5s%>*h_ys1E916V@4o5#m*&ksYs%(n8uK*oKCOA@ z_8oP5-ksId)B~Syu(TA#>cuJhU8R83j8ShxPfvrlv8U17;EnQzD;w$0jg9yFAEKpkxL}En3J6S>KgiW?H_!zK+L?$V8eQ|0b zHR6-N1@RfCB!vd1oN9V?#@wm5oZf;3c8~oJ9kz~rcAZrp64K9@$&}SRC&z3ae>7@| zGu9fc4)>9I6a9o74Zvb|3DD;DW8ICqhFy7BSVoXF+NLjm~3k$=#L?x zF~vtDtvapJ1h+?Ram>$Nm6MKMlX_*aDq>Ydb8LN161vi~9X79*snZAh*3cnT77lCxVDy_;ZEB%2HI^*Bd` zi0R%9t~cRTbe2RmtiFh%O+_)7EyA;e(-yk|4})F;reKQ6XDKC>&XNpJNva6pYy$3` z|Btcn0BGyF-se7{^)#Nggd{W&LhQZF9tHz8ATY)l%OHao#u#Ia@s6Fuv7H?|X_k{_ zkEWSzvqzJr*`wKQ(k5+_bhl}nH2t;xN6-K7z9(UD>^KDU%o5Rk_nz^c?|kQ0_}@ce74+S7pA#zHeEzy{$MvT+x5Kci0{I&8EGkf138aO%8r_0ZY!= zK^zlH#;U5umh>~N=e7~jc5W*Z5+~}BL3>g@Ke$gVQ%6+N!~lVCyes+{lb4@ludJ-x z8;QVlV{gzoYM;RV(s_k~Dz{Zuc7pfz^{GtAy74+jdRHtU5$XsZRgO_kM zSfR8_yCf#FzaO7;09!W$hkr6@ZFz%dA~je(>Z)UBCN}X~Cp21#r9&r`b|?%J?CeBE zh1cz7C^UhdGZTW!dpaUIvCyg`&piC%>>am5ZR4+xeRuZyk4LY%YV;F6L>?pL_Jf9w zre!zPS1=>=tGih3II8^Am(sanWIm?u0y;}Glz2Tkex&2Qc8KKBnQ)@>;^glOoeM@W z+!8XAGSnOC832BJV*N}UJxT~ZW5gGMHwaEwiuq6rDonm+v|SmEsTQOk8lnY*gR(YU zb>hW}VTT&czufMMe3QvCYWh;8_ZUXMdoBxLm^>)x6f!C&@ehJ(M12Dv7uT?y2Jtve;=xs{c56s%~&SGAUor5EHb104b9qzN1sI&8hbE9urqG_)|8|mLF5_1A-Xdb$ zv_v;9SNwbTRafmMt=$sNtQ;E{bFLmvl~^()HqxuWub)2<`xNd7RnDsMQC|1y#t zA;)rtv%`kMvb~;XT58|(?Jg~0<+JK@BbOYmdoX1(KVK!EJyFONi3{Rh65{lGuD||z zV!83g8~LPlOKQt@cI}&6y}6{MWO+_yW22S$dQ0~$AL{Dfl$)7Ro{Cy-i)cHKpq3%> zo>ov!@tJB}G+u@2jMXRw?}?RwX~7n-0+w3z6|94}G^hS?>Uc$hRO}gIsmuoPRROD@ zUA{Ms?8_?dmd*JqTCIpor~JwCJ7mA`>2wNH@mj7hZD?zOPx+Yqixm2mx{O9=Pkrlp zQ)X_%l(jE!bW5gQb%*S|6dfniXD(0k7p06_>^}Ah`M*0m^_HSE zuPZOty}{z1%t=cN*{p%VhD|Ld`_{6IoRH0y7U2`T&KZAAefpZp<+U3smYiFJ3Nd~x zygX!aEgz{#D{TrVSux+i$u{_IwYJvdID*1ruT_x2hDa6 zI)FGvo2)j>%p5$pbLUlj`2od6n-tr&DfZ4R9tMmoXTCI|d{c7ZlE}`;{s`W8MlSh^ z_Rfg&$S8h6|36a`GKG3Cs-W|{rBnJ5O7e|U;fyedDDh%9#0F%cHYOlol;tHlTq0CF ztO9;hdWB#n_KRl&W&))TW^zEH3~99;6?6Pw73KQ+Kx#+Fa?>vJZ5Rzwh&M?HE?^}W1fgGpK=JETdVx=~RJ`tci~ZFOXKHqBHl)j{-t*u~5!K|mPsAEORgdh=3&wP|L#z8GMJKvugk}p7(x=}L)(+I?%L0?W z=(W^tN9Y6UJybjCftCDW_Su3h!&Cgz?8FTJev&OM!|$*}SN`)1yNw}=2nv1b_m#uyha=EV z71P?UxXq+Uis;1gMtl1kjw>tjHU^Z%@g6(5`76u^{CpN$O-VN&=^pOGB3ez(Iny2_ZcAoY6_$? zRlL9FOlo^rMit~%Rs+zqn%qBfnQY5h<7WOBvhIvDhcy{Dx=!C!!?tXxtgJ<5%>+9; zqxW}`L5?xtqQ&kQc#8~r2yd7q3ZMsUY9zfeZh zkB;7Q%jlgfC3Z*?ihxOrxTF>PxeORT?Vu4;AK;E)am2uPnVj-h1mHl3Wv~N4aWGn} zUT`o4Oo8@wh}z9CbjW~&+6=%s=EtO#|8p%NwZu|O_+JtBdKT}5#ESUQSR+OnBIW;q z$imAwruNdW4l0l4?8({AKdvMlI{;1Jy*q2qHu@~>^dX-mDx-?9zrohwU%{=%m6YJh z9}_5@SdN59WaY|Dk&24QCUA4$YZ{JUBmZMie({E0NM&G)aDla=zK|Y{%F6bRy);dPg`4mTN^W+m394P)k|tO*Fi5_ zD=T4FD-}y=8^Q)ww(CNv`oWjAic6Ll`74L_um@XfK(4hri`j0qG^|vP?lyUIdvkKC zIw4-@1F!c(^L<$NUyb)Jbq{dxoN*j9R zNyE6VFR{$xU%4kz=gH5H^qv3k!;$6k2=+oPG;8Tzp!qPJ{)oMBCPV@A4G{W@^#=l0 zmauh1c9y`@isP~7*d&}77I%pVAAw&3zBv=wjAxjol?P}R@#lAv*>a0%+MK3+BlB|>`PxDueTUIPvj)g_>M z>l)a&9H6ctcr}~^v8`7Ey#@O5E%+*82q%R7NRF{D=$*y^5$m^b?6gb^UoQiTQTW+X zqu||%t&-blC~CO$z{UKHVt?iYnz){ z%LRGrH_1AStJo)ZEX}IqR^Ii(+uvo>lS?Lv8xPT&Pdkw{u_S-GqzfPt<&yNi5Pz^i9GL z(tRN$dV)x)fFe;ZM~Bzqz)*uh*`UJ0EymJtFJTv%H04L;mxh*r7R~#Yijzv6I=AEPt2hRr`9c1nJIA2~l>Ce#I_LmO*jfPKo$-I9BL(5j3P=D;%GmJ-H~TFj@t$gH_QF+ z?VvL$wY|(%R$?LATV`+MW`b{i!Og-4&e_j%yx}^g7Hp1 z!_Tx7a23I&DHbCm(FVci)pM6!#%;eOSiu#v;Q1>mDu(;|^62@e+V#gIcjIi(yoXjT zhrjL<5Z5ob*8+u$zHW+*3a@{`tBS?J5_;e1H>t-J45zR>K8mJMw4m5WlV192aG5V# zot1|Fxf#guJ;lx7dw%aW?`H3|qi;q(Q@%5oI9tz62d8J}f*^0YGn~m*I|{Et9*f@+ z^4JA6EOIlRV00ORjb)?wc5iR*bU54-eL|>*eoU0Xj*j5nnKrv(2y;+no;EtWtB zEiEmLK#TO ze>lP)A$z{tXB>Ppi1;cL9vgCx$l00jgjzmips%0eg7;(AqZl91zMW}`v{LS-c=ss6 zg?krcHk&910eA9DY6679sEg~o!s^fv3W#&uQJmN)aMd)ewn!>*=WrYOd&Dbf2apGf zo-gj4t>OiVGf14w56tR}rCpnnZ=F8t7S~kRYtJfA-sadMOZ-5d$nlTpHV3bny(Y+i z)arRdO) z>S0b89pO~b_5qj|3uQ;eyX_YHUMv^w;h$O8uG6(wMA^YX(2JX|IXi{o*C}Lr86nHg zzGj&{DYw4DR+jDNrd*nKqw@WAy7qS6H#lPQ!&>feK35xkxo?o;pCjQWl8+)6`ojzc zsB821wE6!EkeD*skeV>gC%D1J;c{|AZ9aEZEiA7>Sg&VeT8%}(qbc)Lh}_N;p`MaG zBb>~W`ip?^gQkjZqIuse6wyyws2Ooz0fqyyg}D$CR8XSgk3t5QaA!15X6!i;tM^ zi4n5|V;rv+i>5l}q|LsgfgX4dp+uU^+W2d%#@r5c5T0>uV(HN!A!_DC%NALjE&U1o%&ag63JB0m!ao9N7 zzh;&OYr#i{CY2;1AN}Z$3br^2JSyN6*y}OR8|y|FeX#0%}mA5y%c~u!5=gu zQw1NGdZp4=OHGV?92n>WvzrFaZg%tHejjn_{?FDOt$ z`GyMx6ORZle|HxN;)vs&*}(srBY-5K(?md>WzpO{v9hHal;P?AVC#pE#4Nyf#A~B* z`X=dK-ku?!o~1a=N~|O4f)54&tHB=rf>V}r+|{KVS4!?^S0>Wp9HXl;G`6XssAsjM zkgP#VO2or|Nf`jUm@^=Tq@$)X$>8v>KeT(;xfW~-1w)Ymw z=SvRQDfFZZsTt+UP{fvYp$OB5Y0)!|Va4_qRk6p<$zqch9tiS{GNO{bFDIG?Sy#$= zjw6)~m)XxwxlR6fIVX*z>xQK=qCV&L=j!?*%uI5hr-LIK(!#>Cj=yE5*o77ScPUd zu!}{oBXmXz;&PBu^TcF+M!b+GP?qhWy`;wE4oJ70qeTQ}*6w3N?|gJ!rn) zl|1VlcFs0@#eux~5%=ip(J$FI=T7lj@nVG1u0r^A4}E+q93C4R;~}TSU0H?kB8)8@ zMyol!&$LhZ2?-xk8wcq~LsCF~<^C=HqEw#PO`7vCZSK%K15KhHT9 zkemUEQGUrASVV!Y0ZB5YPtc#d8qkH}qH+Lf(Zg02-q?cU_Z~OruSKzjXzf);3M6O}Sx@ zc1y>A@y9Zzh5xJZ*>;P?+ui_~DGWm#p~Dc3p2d?+Mj7)@p$AH|+r$}YS8wC){^}_r zq4H@Q|1yDoRU}dxIwiroH zbOC{d7c4-TH-)Oq008M zd~gC6oC%x-QN|X+r$3@HwqJx5I*5l-uT1PWv61wLQ&lk60=EYV|uU!`{OKEW6+jtvxwK~rOoO>+#Rb55j6sBbQkbACW`~^R_)ziU8Q(H%Od!$p<(zr{L5Nm zUr7eO`ant6@Rumat{r3_e>S-{L969Bt(Lz4EpVJg+k>FHvAw-<1-T`wuP>{;as_CV zfy(wQva7Ezr7t}l@sYrBIVQP};RT;p6zjoQlQ4JY({ZXil2*SUL;-T#NqwG1k?ACG zf)IewID(#$STr9<^TAOF-;9wv_QE>DhGS%j4?Oj4>9pFN&fhOyEsTap`B#I}#KQlF z`!k34>ne&O$!g0)fjuHSOiI#?28oJO4fl>Et?l$mv5PzV#3w!aD z0LBf}2Jm} zV~*7EA8=@#!$$g#mRv@I2dnST7}83gvUEHf4yqTJ2k4*i5kNT7n2{I$4O|gsLLeMXVOmmXM3)kM1lRRyMuw{F|J1~OTVz`# zvUT(}Qkr5it>^eZ)#@$;2*#I(!=Ts%`BW>71Nlx;I`Hq>Jl!#hbkRJx?)bg)byy#8 zW{SH5Yd+Iyl49os1h1V^Rzi1^83~>cu+mmA>q||hO#TPGiIPsz(LKxm+xV%`_^E*q*^+HZSG3NqJnYz9dw_j!_P3a=2FKkW zbKJKJY<{ubmb-s+F4JV{extklas&i(ZXMy=WMy^UNX^>0twU9#1r0+T9YgcyB;F%M z>(5Q#*HA^eGe)F%VX?|{DW(N|Ju*KHr1u-4(n#rt*wr!G5Yr%>>WkNo!bN{%QJ4?jV zufW_4%D18Ym2~1Bpsh3A7nNJzJmJhima^Q>S;ov00LD%VPBno z@<~xh?R@RPKuM^e0OAR>wpx4q z+WCHR+GpF8(1r?hBw_N*yI~$Bq(q}wr)?ShgRmZ0jM&Ym)p#5N0mP-XDtHuHYeS%q zR2^N?un%w`F>=P-{IXDT#vdHcti}>+agml~$1fThBK*hF$}ZPn)spg=ijunZuC<#B z^7Hc)-Q!*RyWk|U(~Mq|m2`J=#oRZrHrA#)oQTsyMx|0iUgOu0v(B!_%u1Wi$45b_)V`?2yR8cfGbb3zAtpS4r{{7`t;S~9f zqjYLT%7uG2JeV@k;!P@d%hhYyqcYhZR!ZuhY~P=9Zg8$`U+#|fo7s=%K7Q*;mQ`re z3JWFLbI<+i@AE-@e6H1e6Zbvm^2-LR{e~%TL5XFG1;s0^R%4Io^%ix6EUn1gP+Z^B z)0j7$Rv-TU_rv@XNoKv=WX_;iVaf4(1Pw!azA7n?vX(N8ww%vpfeNBPyRiNy zqFuDzlJ?#so}b4NDaL~@Npvwk_%5!$urqe$tMlBeos3 z9j+bxU&(IT@N(&6I^$XW?m4;c+&PlXzw1eR*gB*a*QFh(UYUJ zPi7SxO}X#nnv7{{)}+P$X;%}I!Dd!haPA_70+mWpANJXpT`mPTrUAC;Tc|&YC@O4l z+%RK@07wC3AZ)P(3k@O-3=({qK))+H^`yHymcH0{o9E~49q)N=t9=+nq#Aa-Sp??! zUlT6{=O4TXW&+d9*O_-98bNPiY?f+tQ8Y3ds>>X$?&+x>O>YPl7w26Alx8$5Ya1@k zLqYyMG{I*E+kl_cqc4QUB5^$_zh^(o?1#?Vr;#FXtazNby;>Apo}DoMSR{oDP@N5P7n zd;k4MAN>#is59+cgL+%#(H|v``B}9pY30>;TX*spfpC2IF|%r1AycncCrX^l2FzJK zmgG=oZzlCm^pb4R>9;nlnR6NR$oH0_cln9feh_;^%7iSM>dc?^q*3=m@nb)F>!hf* z(9*TY7A+|j;#7eUBLIe|%1K0I6%Hw;Yl0D!c=Mpy6usrl7oUC_{o$vd{$V<8noMU# zuV?dm$YjqjmzH^9^l_GSRd4-q$?5?24caA-Z`i(p**Le6Y`EkrJ`#Dgr{~qXxyPBc zk;oli@sB_LICnSo%d|#+jlg{o`5sh_q%~nX^~u6k{W|A^zAD(n`2xXwfevOt=q1CA z)|dDo0~K?)Ku>|0!j1rM6RR)uw?PfTo}m;v|*^CN&A_^??E|Bct11XtT4) zX3yP z0BELD_Ap=dF8Ie3`6u8`aV1M}<*(F!q{TZa-*S{q+?-opJLu}n=YJwCswSk1$gq;aUktkAMjEc+29DIr! z(N{*z=-0|mMKPx%5V!^73s?om3co|UZ)ovKfaGu{V^0&XEc~hjzhl7GFw&=Q+KnqNqIh|6XjnBKg+f?CL9%&sy^%#oslK^ra)3lJrdLKmn(DRP}$!ENjqN z)OLrmEIr^hDoFyqqSp>|+D5}W-`8zyUD}$=l0V>!HhY#qbN8|=w}XG^Y6j)v7_A?f z6nLPZ^%F*E3THc0_0HZUrDLT%H(uXWIu>Z@%{5x{E!}cGD`_{FbV+S4m)B*N>spIs z26bXb8}!pXpBv>pDb~WoPKk<@wBjdhPX52fB<&w`Ha%<`)#G>xxhLg{G@|aa9QJ#$ znjHnBPVRjW)|*n}eZcJi+QBYSDr|huQKx3HN=j%L75@Ms7S>-5e#WUdeb24ESuG0Rs1->C~ zb~k~jRC-TNPER@tO~aX)+q1HE;N2<16D7)=^ju1Sv(WlLGICCD!r19@Et*!v{g+VI z#3!^t4@*N4%h=Y6M>?_IJs~Njo41yj=}Q;vd?h8w7ZsK)?0bpLI-_lBJKESDO`ZB2 zNC_=HdNiQbsO+1&HYWdY%kE>&@S3LTJw=^E`TS<}`$w8c?cOEk)>iep*^ovQwrdqp zTskF7oPH)&jmdSr<(fXy$p5R6)Y>c=>)H&maV|GsGlD+BRh9mn<)K>AND^yV^E!pZ zT-aN!CKLRXYPvp3_`e^-v=qFEr9-F?PvT_mNjUr4td*#Cy4bY&%`#t1(ih&XWIi^; zHWJL07zd$Pj3^o!FGybD2}?^*J`khEVgd3nV%D zZB}d6l{c>@ zn!}Xs;iQaA#we6J{+LUv-Q>|~J&e4-CdAm+KpQDhIE%FNsyfDdIe~wO0zivVU}Rtt zq#g0Bgwp;NdJWX;<}u|3FuJgvv5aPT*H{6Y=5=L7m&RbQPO2oHEh_Itqc5;bV=-CA z-w|T~u31L-0~{oQ{>izwSfLSok>6_dy7O`}oS7aytJ3qYtBEGCX0%^fXj_XLxRy4P z2AFO^S}%I9P_K`{L>NxS|D}lmjMOdE?ZI)j>U7aLay{lF0j%cV$amVQ02f+?6iO|@ zmL3k?d1tV{9|;IOzpcmbj^XO=Z_}9|*7oqSv1&Aq5ZN!nQH}3J!J-Kd;-Xw5X7-B` zeCGVMzmz1t_G_7nIQb`aR#I`rv(f*lK03Fk+Zif5OZB)$enfS@l)TJtbxnKlpE%j6 zNqnm4MWWO?=8=G8;Kq(P@=2o-ICcYlNA2f#RJ z-tI$ATY&6+HLTUQP#vVvws9T-GU7Armsog7$^^R{h#4?^Jyx44kq0EPOoWc#q$g5B zyPM)xk7l^y&z8iZ^Ih}NIp88veBp(Uj_K1=mO4kuDmPqo(Tm24(osjFFP*~}C@G`4 zf_=MA2C9?Ano-r^Y|XPtnL7+!KkS@XW!C94_(KYA#5 z|6_vNi8l`YcZmDyk3S;l!qGT{D&h^(=y(AJehSM6oo3eSc`X3jxwOufKxUKDQ zmsZ>LS@a3^ulv(Y=B4!5dF27_ga0=hK~}sY$rFv;zgM8C!mFQ&aJpHRM9<+0FQHV_lYDT^8ap9l-b! z1XPBiGq}}=))wa7R25V&yi~o#>ZhcZuOTzE(?EJ#Q`C$*FnQIzH*EjzK|m`bxMchbFNr_VB4_r2F# z5sy--wEgJb<oe}yQtiI^8I5`-8NB)mK zI}b-#whwrXM!;*P2b`H}oYe*CX@ToI$9MSvmcC3nxAbs`RF;opjSB}#N(SIZs?f*t zAmV)q{;ffz;fE)gz$6=VaNsQYM4l(@eYE1UQ?BEp9v?H%Pkc~+5?@dni9iAeNJVj( zumRz4K)n@crtr2XNMhTS(PV&wUIwLIG6zDw5WlgK8#m=P9DW3!MBf+ble6Lll^HNA znRkndB^jB7==IL!%bguL*5e6gG{vx4Yz2#G#VAWWbbga{RIVba{C!59L{6-EWQbvu zcc~<@=U$d^KPJDK;w$fZ;k{#Mlq{RPcbM?yZo%|>L} zppqe0ozZB&XW;vYj&*a1va-qYiKbz%FPv;5OvPP$HJZHx2Q*k!x-VbE`f~8in~}Lv z93z(bn6dLjI2m^5lqoAR3al92oLHU3TR47>g~Lq4WNcP}l_rKU$U6|u6cxUvC5cj* zT_#Ng$}v|l;u@^6S3SG0a(l|*6&8=S?Ef{H_!%I-~)tAlx7kOSrD|&D-Dzh+Due1SuEHH8}B3m6}gP!La6); zA4VLld4vE)QCMJ_#^Dc~CxZ0{#7lt|XIaBMKFx1b73Z={e{*XvyVliLU%kpzl$-6H zw3ytQ2iQNl4Mu46SuW%0TYjl!M$6TP-0itl_Nip0Np%zZ9{mZ8`NV`ziq$mcMf^N#I(UaN@OGU2!D;Krd}nHgmt=sTCyC};`fZifc*qSWX*KpR-D4EfydH6zPuOFlh=ylzV&}I4 zPT@jtF<)n;#!KvsM|O`rgCmFk;Gtt!z}Nnr7Lb=0_f$Kq$!JJ0%Imjx-E&}Ct zp;*c;PwA?|wg+~2GgoVsKPl1HURoiO-&jmE^#?C}$$rhw!K@PJw$|Zc+s3uc>8XyH zlAdtV*)7?wX_7ElZTAO~d#Y0}oGg5}#_|0W-&I)^$?veLql*SEhByYIm(fo+rai}R zmJVo2IE`JMZP9lY5{;4xILu?U_1ZP6OjCeU_nJ$RWc?D8Ho(by^(9H{fYRW}2!vMm zdp%~0*UI)~=k}YU@A}ehdJmD`jT1iNr+W z;$M}?-=!0hh%){9!nC)gvaF1(bWeKO=-~WWXRb|MYg5S9J*jG}M>3OE->6!#JQ(bZ zUSd}dTk;D{T@J^TBYv!-_t>~i-(L&bj(M%sg3^%Q+>;n+RH+J9`7>)uH8}5e$l0Eh zaPZ7@Y9CeD?G^g^A!|U-v1GyiJk^=^$1*W7m_#H~23v?F;)M2Qii^g=9wtVghzsr_ zr@*SCnk*nQ)UAv0Co*j_`Q=2x`0rFIn@W}A)^SFYCk)yQEz6At`#ZuGv%hoZ#%}&& zIXM_mY4i3`tr9#Aeue%T{U(hlX+w!%K}vTm4j5q| zq0Oje{vxZ+Z*VWKF70zI%geU28vSjpsb-S|qM^X6ii;e9YzyY2GnzCu!@n#w-(<~_ zPqfN|tK|ceV`HmUZBS;L)XI$#_SoSJTUIt_arhHx%-9f%8K0stBWgil^j=}~i(#xiJmEJuETzz8s~r#wdrFBIDRuHws`o{O;!aXg5H6Ucs;o;QB;WuM@x@Z^G z-qhPSLgy1B405zu1DGMq&5kkeNO9?yXT&+h9w5m|{rVi$AR~KYJ(>9U_xzusWXCB? z8^;qF2-}5CUDMN~+ zyYJFI*1ciN_UVzQz7v6yr@pBIxz7C(C-$cUYFWz>u>-Ra=5)lmqokC~F`tFwOfRsu zFre~z^NAm$WO$T^Xh4}ZuYMs`ejymT04!TjWnwzj>S2v0`imfF@`Fg6NGF^IO%-># z!i3pdRbsEG3?wG1`KQ#0iBlwOH%*(i8aEyIZ}c|y%k7DQN+dBqpb!4_?8eaz8=g;4 zH>mOcdXVX*)m~G2^e$ntG9b<9A35^QAiwTBnM598Wsuyx1W$@5kN$=!7_6?Y-UZFw z=zmDX{t6;IWc8${_t15!I=)T7a764jfM^pvif77eD3&1wUPA@XvvAmjO-IlCf=)yf zPbhe$!^Sd-IHBnC7wXFiq=9+cc^(ix=+X}10(mf~N;r`~e{8I_@_#c`>&X2ot=7L< zsj^8!nl47BG+RrJ7o_BrIjd3LS1SI-rSmbQ!Y=MtJqf_7|N}m zylDNk+@JaXJ#{XrCwiRp!ND$G!T%xKX2BDzRXG0F`%-1FhDx;oxu^!_|B%El#GRB& zAa<>7gryYm*mF-Ghn}(`MbGVg*TF&ZSwJt-dUrTuYq8=tBNC|-Jq6&@D&e|RgH z%ObO+QxH?;k7L)87O<4B$7%cY(CTtEIV_>25aYeV6dBTS;Q`*BXWR ze{O4+NTu?&vTAEdzD0tF6eG|J3kQKNh?_u?0bo;n8UW(qJb^BA8d_iv~jBmGMz%YT@!)#k5D#sqr3i(i_Ii#|DggMw+f zxz^*($}Y60rGo|hnVeb0{~coELzq~80`{g3`bYQ^^SJl_`ug~h>#qCmcNv#HS--|= zNn0L3UC$C&o@TM;4Gu~s1$d&bj||k-UY(bBbuE6Ft|8?fUVyzQ@;PbG z4DBoW78cYG5%;*=gnOo0k{E|0n+$;I0jGi-2@511_Y<7YGGwQxSYe^lF~*HPG4}wO z5p!*X|GCCl=)b(V%M}PT6IS zb?&xIS`o?Q&l0cexSuH*G&xYt;)qyFTip71ova^yde-C>c?y}-Ict#FSEOo3)VAD0 zwqIdPOVubcb^>c}OG@fhah&R~*I>wcE{pyR;o@Dj3cjcS`4H8`> z%_UtNbA2#)J9~)Z_9>pru5f~GK2p7%r_~VL`P1*c_ckWAjjQh6 zxzmJY8ZF;^c13++Q?s5(*SJTsMw7#7t49+!@_%@NN-j({QEO}yJ&db160u))>se#r ziRZ;JYuOmeR;=yTDm79i)K%t(vT4~-MLmYH4Xr!$;AfxRv+s7S53NDH5{sL^T{j={ zJj8KA5(jip_?Ng?v3O8eF50UWLm=YIhbSkWqELkZL>e`?;6_?GtzN@1Px-4ea~ys4 z;zGMF-`eLW@~VAPg=(3}Bqf&Fubx!NGxovwGLiE44|q zjuM{*+lcz_c?lOMe1}HA)Xl*;E(m-jC~?FCUx5?6Fb|+9ut9>~65XExVQ{PfASA~z zziFmfFRkd&GAELa666~+2uVSlA!f$H3gQ&fzf~4LgmH^c3KOs)CdB@8^@D{C60KfZnUmx2J1yXyF`6ehy~k9MtTPyO&Wh2|p}oq370G%dAx3?2 zMRGmKRuqTUhO!Q=Wt&taF~G8c<#<>o{uw4LUZXF;K%zr;Ed8$YZriu(dpjP=?0evW zX8yPQXYF#CvR5G?cH;UuH@Du&=`*xC*ClS92DOAM2MXyYnC+Hy>&p2-$1RW zqXQ4P2bI{DaUHfiaMEcVYZr~IvG`a9N+(~Kalk#E$VQO#B9w-=jP<{qnB0KK1xa2J z6`6_M%M;=a@ztgn1P#bfk~%oh+S=3F>Q$)>YBe(uf%L~^Q0GWo65tFw>^8lbbF0+V zoNP%W`yEq*#a&jxXf&>eyqt#9X+l-!D0=GI<&m;7dyYKJZr!NrW!YYpDxr-TS-w1a ztZh)uq@bhlBQ@|=QS^f|MVs@N)E#4_t0mBNOdlMvJ9zeJIK@kv`In%cspouAnPNI^ zNqFsGfB)dx-o3k{|F4PTu4~Zg8sJ+gx9bMU^|7;Ejx}rs>yq*r&O1|X3>LST3qvFz zzS?5kQ4K5%VmdKyv#=LqDgr{#C~%pn^yLEcDcq}5{dTNFlj_C*AM>l#Nm$v-*09MD zms&-NZY`=S_8OgTeK%RjR;z0Jxhb@=^1IQJI;HKOy?Hh3i(p6~Q*d)pU9Q()cj$to zi>+d5ldm_fB@=xL&puCrSIn&M>|eS2)uJNa(kSnjX^{YyB;B@@WtWmX?YeDvYW#_9 z>$C@=-BTlxzRt;43(41ime#at1segv3j7M47d?ciWq-mYr?C>?CB)$}zYx(O)CPz$=D_h52o2cF) zQ;_j}O1rZvIqdbWaaY^zEBb%TYNUk7^Ox1Xw6tb@wr6YdS<)2^BBh*(#Hi)Z13(j` zq*h{>iL~rbudwVZe@sIBqPPE%{+s`xufk&5kepGZ8k; zGB~XhZf|GZ6*5^?wja<;(P(YDwR*L!y*85J%S`S!vWhXpl-x9Yl!%cm5;Q_0L4#T4 zF`GsWVH5eWg`uUu=iq0IUMi69(RE&BE)(^FbEVRo>W39h#C;Of6XswwMLD`FO1L{Ky?hF!@HYE;@Zj_UAE7~ zh3xW1OlZ&~Kgl*N<0eSct}x2LCR1uS<2?4#_ogus58jr3UZYmmjN;*7)l7y)i+GRF zLCv`Gn@OLNfxhWO+69s^SpFcCAE3q)y}#o}q-j%xD3&T+K`!8Q;rChs+yielA)Yj) zmf{au`jYx|vLv%9wFIAas}1VROgs?9+59cvn;IB>kEDoyj5dOY2qS|mGwcH8bzCdC zMv@}P(yS9*RJn(1+CHUSR3G|Tc5f=nrty<3GuQ4KKUeLW%h0MNI+J0-y+@td=NTw2UQ(e{{yY<1ZO)eE5{o5JnQtn!*mvzB z+KmNPx7!+)HuSBilSbCB3O?_DD#>!`PC|B1aLZyrL^u{i#L|uEeS7x?>$@7tx<@%k zDo$opaedPJZi0RI0j9n=Gm8Jp9`kNxCmGFry%oa|nC;x_Rw5ALh5x`w^jv@nsZGiLJUZo<%I! z$Q02dNQ-^=9yJiypokjgw>qFIA?hol9wnha{E1+C-{5a{5{GHmm)0`3OSv4z}sfz zX(%s6U!+?UDjw_UfBkKE%f=syM95VU zC};OZ_zmst_bgj>M`I(63FN>&Z;-r(+(MkKNOiZSDCQ}!G=Y39%*{k^Pr*x?!(vrv zK5Rulq-b;D!{XW4yg8tnlqkpxt4DJ&Ir2NI+k28R0MG}`gSvt(h0(XSmdv0x4H_5B z<5@+ntRzG*tCNz_vzM4W9v2xxOk>LB@R|=bO6VgNqA0!SfLVW^;^%DQj8sYFj~I*> zKKv)$OFF#2SK@Rm$-U{Oo9_4y#{hAQe#wzb-1Z5p{wh4vWdXYb1>YeBlRSmLb5w0D z<9~u@Y-ZMrHsE&_yKW%E<_TqGrRRn`NjU>w!f0;7N7DYvw&fLc4Hf9Ky$^gsv1Up2 zEmuM(XCsYQzqxXW>pw33mDnFcI%mXF)uY0RiszX4R~lK? z&9d+G_4)mN(%swZ>C4OOL-bU0{4U_1vRIFeA397q3B};@4`O#xjoFh}42vTwx^smk z!IjDeLtz4uy;wE^=t3849-|Qgg%dts1V|B^0^NyKhl)Ye8P8;b7b~^TL!g`R(-L*33F6~p+r$O4oPsK~-WFUGmA>+E#tcIv0~JFECFsWSZE zxW~70+}80~z{mLzPDuTY`10xHrX6_W^YKNdO%(EtK7=nvP?}G~0sl(N^ao!E;(h)4 z_0bdr!u#;yBNpxmci(+@ple2b^miq1#q6F+n7k_TS<$Y9sn!8s>;v*7MgF*(5LSf3 zO7kCHN~{zN;-Zy|N%3MEE^q`C36R_kK5t>xR8|(Qom0hA{Ac3ayPn(c$MnPNpN_s{ z!fQ&Uiul+z^s>&rS)t!+e#YUQe-)&v6hK4FJ?WL!nWcY_H1wx5rnR1>S6sk8SH~#y zo~w#H8(D=}6HabXXws}}J$v>BAvkEy(0-x4^r07(T#kE? z`cYs&)RSJ9Zl5g=`5oN_n|+xM$j5ua^*+;*ilN){yw22tT8k~Cay0r@B!ZFiOilD& zL5N=HFSM8i&c)&xrd0RyjFlbmphSi*#ejvQNNKu=D&j~40yUwiDwui9`4s%25IbiP zYtgR+rSO1wHnxi|li3-`ALTnD`4O_)H1y_Uk1>CHr}k#ovt3=U`tDfr82bn2e)SVC zvJ>1md*_6I!)Y?;cQO>Pa@wM<`H zs5JC6-a@6YLv&`#wa^t)ApSB7dV^45pq$45)!+~VPa1MULhqEedVys^)&ImAU;*G2 z#ZV1#8YSw9TSowP#pR)WMp!{AX+aD4LU<;o6#~-?KgoEyt=)s1Qso{=QL{nD_SIj+ z1*;Ji8)5~Y>m+Vht#dfHaNODM1!e=_!J4V@c^h3VW`l*(R&!EGm+ZpiZkK-zvkouBtzExi?fs-R*cV+70GFwzK(5IjeHPatE%`5q zQF84MCmR|o2YR+|=?}H7Sv9B3b`QRR7L#Qqg-cd7c4{>ilJFMi4Lls&wJZ3rGn;!W zb>xoPEQ7w_rrRu*PA~RdIoABy*xB8JYQ$b3zNf87po7mWha5q?7mem#f^%V zFT%1>ZQBW8jzRytcQca@n8_X3c^&( zlPi}j;Z*CG@~aZrpOhzA16}Rr>LwSXy3gvGwXX`OSk}sa#40?t7R`9Y_Y{fWEo0WH zb(NLPiS{hxEyEk?E2}$Afk!7E{Rqr`#3L~-8rmus1u4Ql2Z%;#lUQbs5-Z}toaC|SjdJoa$_xC>U6`h`I3NM5 z3h~IkZKF^b3iH1JSjY;EI{d$Br;f7%zj0Pq*VCCFc{LiXM!k!Z{wqr^_k6gG8y)3H zV^7aC={VHcd8qT3G&ETu1}uwPN64&~>oY*BRiUSVWN)ldN6(}NarX}gK^Js599~wE zoNWEvYv9JboB{i_8vFJ2SbjnIDZQARe#YE_S`pd}0sS68Eusk!Z6P(AR`fmq*g}4b z#DmUO5D*1?zMxzYGo9vhMb8oyu$u{>VHfEb+e~bd?GiYk(z(JceEutwab~Y z;=q9whBc%bY03rUay(Z}4W5|abR?u7)9v5rAL9mwa&m^S=V%{Iya$s5j%7uU`N@4W z*mVR;;zw2+_rdXH5f_O1*EmcomI(x}L03yiV1>^l)TqWgd~u+?=C?)p@E`D*3i4pd zbV>B5(HO4Xn)vU@@&5lQwF0FQ^m{9+YZ2i5*lap8I z%nW#$j}%PKg0>n@D8xsO;f*=cQMfv@c6zKReHAOuy#Cx3c~$%zg?h#AnY)Tx`qr-P zGcNTN_k}`zeAX3NE|+#l%eZ%hLdf&;pbzo1L@DG{RM&PHvee6jh=pQB2jn~WK61o40`oda{3Vgj!J zDHW`-`i}HhEh$=Y$Hnwlv^JaJl{Nh5EZL@!pR1LhYu;u#m;ZFrGhEV6M$?Hugttz# z%W<73pK#1&|DUt>0BEb+`+#{)fP_QH$(}$6guOSEP1$7x$||K4Xb54ILV?n~ZLQW( zZPmKh>-IXXTKBzL_q^&=dxzV)?$uXc?{ziD@AsUOl+k+c_kC%SlR^^Cd7l6L{gDL1-m%l+Ku;%s$)rBp z8ET19B_=w{gC-?Z9@4nLG0EM`4+6gG!w1#_U%dpf(C3}atyZQ%j3?6N%$~Q>K45#n z?@vc%{(pWR^~Bf&mG_~;E3^j8r3ud}%S~ch@;PiPi#xyMY(HffF**7s(W77RUS;UQ zpZ-90ojS~tofyhPR`Qpzl4kx=(#u~*SU+Sxw~5Kt^so}jjb$g#_CxXk{i%o|EPCtW zAM$#jUYwKoJDvO~2m>kN=rl)0=C#@BPBhRMx~M=zx-wU7X zSot}nrRS8`hX2K|r7KHuZwyXg*@R&6`MwC6cNWpUl#r!Mf`h}5x9|i?YoYBp&ktOF zW9n#5=PkFl6^=yc&=sUf{C3a1Ve0f{eVG#B3sV$X7{);{h_YX=6EHBbW{>uw7o!O4 z1srT%O~lz^i`3(mLJSG@dZO+_ zOF}wAwAi3>jd);gO&du6JV+)_h_jG&21D#nM~u<9?$$MNYsmUdo|~n^K)Q~EOvUj} zbC_7b@gH)3wYse|a0~rIwu{HTA9!AsemXTbB&T48aaZ7s*z`Xfi-53pBh?wi@K z^h~{moFhS+Qj-U{eOOEUm-hIiZ9nO2QN|hsZ55@D~?Tj(WSCi z#}vp%N#MnOmV#b$c)pSNc`ii{^*W2aE~;7LBC^P+*I6d&&pA$p|H%~+(`CKm1%*R>rci{{9qqZIQEebF6*ucfGxT zDxOLuwOh7y6;6BpCVrDIs!&Vb)0hz|6>HrYuHcRpvc@ z?)@YVtwD~&I;><_ZlXD`Ffc?C80gQL<&pWakPvA`V2i(%3Aro9p*1!(8d)3W+hyGp z85-a((HKFiv`5J0Le3qng^Wi=O}^i*g+4tje~>1GA0`xm2Yc$rG=tT6+iNlR=PADu$Y-u@`n7BEC4)hfJBG#CaS3acexHUfPL_{(Cv4x~Cqw^Z*V zYsm|rBNbcOZ|V2kili=@*HxoHg_Kr7j#Q5K^|{utW9!&kWBQv%`Qz-`QGS&qh~23V z!A(yO_7p=%hEq&8qz6nT(;pLmy?+@-EOF$CJv`P!XjFKqFv9A=URUpl6L`911LiUD z=g6b%5`{v~KWR|lW;k|fZgeMK;9#P0K+r_OzeX!P`#`X6!VRU$}7ZQBy?`=f$Q z<*U_C^~(cgBKQBld}N)6v{ zLAQ{;pjP4CQEv$Dm^W8@ihobki+UlhXGC%E~yi``U!gu$)$S{m<2lvoJ zZ@j^MajH3`qa&sH9;?AX7!`Brv18oOmS*Jl+nTow2{8zn;Mf0N;N>s@`&$YO{(#>h z>XnIRC#Af_F+i}dnDtLRE@Z-Gds{jz$fHG3^Vy`U1S3+XFlHUtHIjnLj^hfqyU+gn*V)bKFpx~?7?m98JU z+O_fT@yL?lA5gBv3=(oHI{VndGcsch7Xc~cl&0(9LU2k;?2n$uWM8{dWU?68Od+V| z!%&hs93+`bf{f_F6T}~&T{q$KdhTgj?QOdWKar`5kB^CIPe_Q5Z}-$n8DhFyF6X}{ z;Zb@c%6ealHE0=GwOVb;G1F$<<{YFxF~hB`$utg947=6WBbj;xO~}m2FEH zX7Xm$kqLiU>n=@qSq@9KAGND?Hdvqi-gBGmhpDB-MQMf<|Nm%GF#hnyY@*|3!znCd z*&vp^8!mJc$2DG395+?BkztZEx`QNajosAD#l^(4(z=u;TUCH8E3!cywrrU#&6Luo zVwShI7I=)&p)!eoyc%+a=7>EC-k`n4XcxBz;1$!*bV5JNIVmbg+=W>RUR=?e4mJmQ zEzo8(#5FH4tP)xPP~#6GNQk~rse(_4)*pnw1j^2bBc4-(Mr>=uE{idcmI?*)Xf^*x zeU+r)cP|FLIrhdI_W&RtQu6005h_`&Ev+wBD2hpdg}5t~8oJ2kxTV5sGm7yS@i_5OnFaaumP%1h(r6Y!zX#dR8F}mTJa0_qP5Pth&X=wZjPs|@~ z4-1PgN-@VKS(%3d!j;BMyHy)(ZnCUsykYXTrga>D6W1gOOfO>nW%`Jn`N2Je*;f!0 zsPBlY2}?>&iuyYfnT)*TS$2qoOIPbE6ooh8<^?_HDkLai%b*Y!d8GBl*V>H6 z;GST{IGd`Jb{Z}6425)cc5A((qQX(13c;n(r|FDk={-leP$teCy4jxK4vmbA)2pLx z4?7YWo!RK$tkLMyW=iYHz{{VIgrH(J44Dr3$U<*MSJ5HAqtFDxx(gZRsJo`!r@iI{ z$dsZGi~c|NZXLWE8hh}*Z%&_J6VCc`YQ<0&2SN(cC?M8e1br~Cr5bIjff<4}v4IIp zYGxw|J`_ykhhY387hN5BGLjAmKZ!`BW5NHv^S)2cJU}Y{P9uqU;(w7JkmG&>seF(A z2v7Xa;*Us|Wa&`XMf!VN*Oj#L|6?NOKNcuqrc~<4H-h-vSjiYa9z={)Q;Z~#4MzG- zyd2k*i$9LP+D5;tZQg4PDy8p`Gfnh2=vhZ9Qw2yMjlQg+PibY)}6 z3It-|<%kvzgu3AHz=y;pcr~}^o(mA@X(RpY77oqipbCWwBY2qh_cg=Z4}7N6pd44>=QRBaq%WNSmlLykXwFK)5UBZ{tN_rD#;nE!HLv|8Q8y?F$; ztEIBwz`0g0nG+gIeOiChvdl6IH-B`uLmz^;>%*tW%$H;rZ>XI&qCpQEaWdQ+m;W zY;3D+Z*~z(ki!LJ$%T313sR7`d-w40MG=3uo4YT>ZI;M9hh;?SWGejRAyNs$NTl>I z|241jsfFYQX|R@aWgj-Ha||ts^?@dXu{2_y4hamkH`0IB;xda`MT*J_8LL%N;Qddr zRs1EV8M50a?RN4&YiqopT+qJ{OBA4SYS$;CwtS^f5lyqTxB6YVkj~GF>CyZmA^kd$ zP}veFjS9MFU=b~ydsZGH zN0JHuWw@e66%wL)D-S>5w*@6e$_lGX0@N)eCgBsS8YXPSfkdSWdLt2WK{~6D=Ai6= z4!_aw7QZ|E?)Q7t%Tc1x7tBu@OrVxO@o^5dIKewuKFT{HD1Dk$0&}rT6bHxzg6Oku zq17p(NJW^jO0>+>floB6)5fcT`C#_I$7QplLCEISU5 zLd~*cFlPD8+_quE@B0Q@a*FQdkE;?!YV?ow{KS5G6MOeF>}$h0WT{daP0~I_mu>}T z{g}U;C6hB~Nq-UjN#VyN)vi>QWv_el&HL{Abl}%WOM5Tpxrqz>XLyJ_S9PB-X=w@4m}!X4#u(@I(&zu^fB)&L7KA1SzEseMDrXS!L1I+A0HB9owKc6zdAL#abpk zmK5M`prFtaYYLWYOD&dCEtE=Jnp&x}7f5202;NqxE7k=|$;C)g^ABcC(P>&OrV1n^ z^Uq@5x>if}1j$*em6ZoURt5_>3de=L73V!)gWdgu-*La^{a*Kb_rG%=ElBh2`TRJ> z;{Er^&PDq#Y`Nfi`*ur!gywC3j=*VjKdG5V+51cp>f3#ajRjrGervg9F_JKnB@#8#4l2x;rSJO5}#Fls~_ERnWiqdHt$ zWh?iWE7gsraDhzp=p|;UDaW3N##q>3U8%uPs$()@D8ZD~TFfk~7idJK28{&C1iQ4mw-*V(0U&8#KAg5t1LIHx`X&mabOcs26(NB`d zAg0~{A|yr`nCrx&376P{-`vd&BLuvRXjU)bs#IF^x<3rv#pc(V>Z|8k;ECnCE zf0@QKMPCGQ}b}j{xbFvqmWn9||t422*7eA5AF=^<-tx4n8xViFBQ=M?4 z*LzQNHI5|NOo&<=L3Z5ls;TRrMuh{3BB>b9s+JH3-0+;7G@P8ouNnC572X(R0emhY zV-SBGF-2iivv(PhEh(O?55pGJCMw0igMuf;kwD26KtehNd+b))`%%k4qJM zw}xdjHJGc5)FB}!*>C;Lb^QB531e~l6I#>7pf3%&)2sth>(Xex?>|2+)FKJs z1{48AE>{lrhih|t)ao#!zDXji^P^bje+hI(=Y9THS`+jQOfqo(*=Z4oJ0*f#aTR@} z!T+1%&f~Zu5x64(;xP&oT95bto^w+YIqF?`7hU-j?s*P(C%`%4EpX!B!^bFnNc<(LlILehg}yG zEU%2%lKKK!?meL{@%ShGE;Y_L1D|=7Bc^pT6^_pK$@2CKCYKI%tiGJk6kpE{@x2T^ z(uPSAzXtHxMer+VJS*$$l)Qk?KqK>6O|Yx3!PExKV_D0%?cCCFjSG!eDX%rc zPrU+uDn-rnU=ac%6K3&&bwuWe&ndQ21vSIqo09_IJ^(8?LBZFt2^|6PSwqgVerSHp z6k_@v=XsiA-ULOr2^4)t6Zxy!QM1ah%f0$a`(RyaT9iqu?_$qU1x?905`W+2#t?sL za&moikOa3NW&&p}w6tWNq+h_F*I_kIMCTTzj)g_rQlqj%a@OR>rlqYZw6KA(VdWxi z+%35h*F)_m$hF_(MZtO%8S!+R0cr>#+Ir>u6ZRx)nGCStt6K85*^=H|XQ?T%jr`Gi zQ`_q-Nt#;42v;VYo@Wf5;RdzZ_kYf^Mg#_F+$x!WDCS*`du|u_70FteEI=v^j7(8f#v4phxk9s|5+r}| zI_~Q3%F6Dmxpg$aEnK4mH09fas>Rm`5$=x;vt425*=+Ik;nfDWzl^9G8HDFU!!v5rEd}ZJ05mU9 z%-^yRY#AO`-?(O=+g07zIy>A@vuyR?p6X@mYlp*3j$=2bhK8ezxepE9qb@r@L4JS8 zAMyIcSQGkk!eWk<2ny&-o`IhPm~Ae`3>Tm7rKmQ?>`FrPa;*DVhdL#O-83isYqcM}r zo+rPGWPV355E2C0#VpM9N6kQ0u=+sKtJi4c{m3AyyAYCOswx$&|@JQi`E*4%? ze2J7#fld*WM0!ikLe54yAk+p&o^Hs_DQC<*4WYwYZT#LtaRz<7K6FJ!O6*MMd7(?J z0~L8qxz72_yn+)n#)TX?FsxT9aPO8P^WXQ#)`bQ~~0RBN1nQElR0?c)wgLR~e<&QNu1S_fmZ zMdguXX=GWIX+((SgP>Phr0Sa>Z_h_rzE<~V!4KA-PV(kc~fn^@~2CJs=B zk9ywk?4rrzQ7Ru)DlM&bgw*lx(&wIMWv|X0KaO$pR1GeWOXG@F3fy~{nwx7F;i=Zy z-0anfK3<_~51BN7<>$*JD7e#weqwl-G79RkenVk1x+u^0?DN;Mx825)e;j}0kycgz z{FU{pYd+oS+~r*E#C;b%A*9rMc{f0aRF$H_m}2(W;@>*60IyXvj}E*>=vY5?b(V^U|1N&~jEVt0Kw$UDGONd^ZJirEh92X?h#obwy_!?_0 zu{{-bt%l|NV9%baaX9y8X8$9wWov_P2TII zehVwurj?LVut(13Y0bA!g}&SZq0bq9HZc-(mZscr6po0-KJ&PEMBcaOaWMC-C*25T zbtjFnm-ze7BG|z3ZwAWV!JC+6rN#P;G{ec0Z~z>BG(7*R_*qW|j{O+ahws?W9i8=P z_2ap&PPN7u(#c^=NCbbPAuv$BoDE=feRlh-0W~YQPN}e;SD-fqwZUl@!w%q{NOAmc z*9v;0hLzwsoR+{az{C_MG0EO%g88Zf;eO8RM84?aOK{?U#Kr$u^}iQYqZTGJ&5&+T z1O`sH`r)nFcD}=Y=&F)BGbScNoUZ=H~QQqcG-EPy#w zDR-U-AoJw;S#bw}8>#(&I+X)nmXwCIq-48X{X^*9Y{G!@eTV zr|eeg3U(~u9F~#u-v!)*==U(URr;!*JVlPXgA@(ZFTfN3y>IXP7gGiKps_K#KBln| zvvn<%FetsJ2wNQfr7?-ch6A}JPt>?M8|AOpypW8SOe^|g3 zb6yd$T{3k)`YF|+WNWxmxsLmVTSsmPrI{6>{QcswP%i&7f>U|ozWUa}#~SULH^c6& zt9~SGYfc&&B|{dankbdwH}BxM9prg@O`Z5H^x%*yo7=QJ3L(-?_=c2NzNX;(-HI^b`TYsXh>iId}O zM{h5XBt__3;)jDCV`aAm)$2O7F@X|ipnsyab6TZZ&g%aY>nw3#UO`giny8oveKsaJ zRj?8wmj$wNmqsF&OVmlqLSzV|xnWSzmN2y|DXLYaa}*Rr#ipAI_;=K5IZcZc)^SdL z=7PTdU#+8H+C>R+1H2=~Mq=kQo1k+St&Wc}jy2!^+G~@?*r)!7U9K6?bZJJXU*ZY- z@{R0WlMg=lA7mvdZO>`1sK67#enai;QSFI|6rMQEBccwVF`)`nz5dth5U0z6(;?sk zKo(RM3alYQ!lKjW&32(aDt!nSMlb$1F76iopF|V#QwUM>U%Qd*M>>%AXEH`rW*U=n zbq#;FMOpGNG3t%@-0Qn7Aw&W6>;<5HSYH*<^x{P9b8o z)F%WQ<+XC>LYlAx1So#Nl8KGRX@-!P2ZpeTHysYf^dQEKBM!0DPFvPT^XsFPN>X*y zD?I)v9*a3zzWXRTY~*@Q1d?ads!ijC`6*G6v)_dz+v-glmbZ&igE+zya7kUS3kv%&&@?upL|A3yW z4U3K{F>y)i^5`X`u7S^3g**p37P0>Ff}BMB87< zxuXDaF}O-k={CdS3A;RD_7&2Fa?EoUfRACJ7I z2T^;<`Pp$Li+GuyL7)^xd0wkYsE%Tef9d%>``xqb>ziJ9;T!Uy$CK2sDX%2r-ln%$ zg1FQ+X~OXE2g65?4wLJK$J+RCGMx~;W$mTI1xtW9i+}Sv%m%grMM}i=3w6altOS_2 zz;j?s@LNExK^)WNWkKc(t2MK}*j$oj2nu>H=(8tS^5t!>d-^vK{#MKj2NA9nK%t-_MDPlF zLHsXLP(;8`SP0ZB2$4!(j~!hU(~SzE^^uvRq9Q0D`iXH?D${J*7$pf%tW)?)d#}B> z&8SZsIr=ZDQj0c*6H@-j6#tBAq>WuY-P6O|B<MNxk|V4E z3Ppf5BAHat-`t02ymqW~*ho6PduKZ-&d-FkKs!;P+3i`@ zRvH_nT6lWCW?{KGK1}E|Fe+u9oNZ0mwI!ZRn9QkQ_|(!!0c zv*QUCizGNCxFsP;8=^`-%R1$US?=R|xN(Kz1#{IzuUb;=f-~I#swgEOis{T-gBHAGIuX}SyvNIP` zsDwPpPjYH)_TB;mA|>_LeM4c>zY z(Ytr1g+<8S)s^edx0}Ygh9XIo>$A^XRPH39hwhV-aKBK$B#~01)ZQHxLjAM_qhS8o zpkQdm$($UQl+GL_O?xthE@syq>hC_%$B_#?t9tntOq(D8XIw7Gqfqh*|H7SvGi&c8 zO4K68490R7y1&r5EBh^^#r?$>qHI1W=#~ha1GEb;7+6zYEicj+op(xCm#!>Gj^fn0 zbSi45{*1E<_5ag*Sw*{L3Od$M2@14a<#ysdb%uY}BO<#L0Z*ucbn>T|Erq-;9 zal4tVp54sP{O9iPO(4NAQsE^*(4ZA_sveeD5G7CZD?MX<#1#S$AXVe^Zc$34>tsU$ z@0n{9S-8@=vAJc;*1Du6hS6!Vw=BWrv&U|5x{n^@NO`(e6`*~vCK$!mq5Nz+{}+uOD%N0x>8 z2S}I@kH17)(RkRhrm#rCkU{_A=J5Axr|fPrvN~?6_HX72{x!1u)IOH%M`07Gnk5DN zGlZ`$YzpuX>EZktV}xCyWtU|xi5ODaB2xnbqiv?KqN`@xnzvk6y3W9-x!s+v+FD2< zN+Wj2cRD*v0y{So9Y?VCn2)?5dW(Enk=AiFne+5Ls@cTiIn`o*V+z(~tRq^xJ-w{d zl97|`A#yH$U#eQkRI@GALTP}|ul*zUKIms9|2LfrWHZ~@$&5U8=`X2~kr-m{hAWx& z=iSRzI$BoL?{brDd;lNhUvj$%%G>zwKGuvC4(iErky~;0-nj*8=qcnZP?PGkG!ye_ zgfsf zwDkk*_*6#+xqeq?Q`hbd*WErkUA=zfrqt9J5~NW_7~*b!_ONqTR$J}5bkA*S3|7_> zhgY~Te$iNW+Cx&*s|e;GJV4W%y?zX(oHK1<=0jFH=FH!k*EZF>CBap*%<>M)=siQr z`M+t`!El-$8e%RjDT+RR<2849o+Fp9>)us6Idf=IX2r zKP_*X%FWptzE8FxkXiQ4?Ze&MiYhCIou13x?wxK%>-mS9-15yW8&+?dd9@3amx8@} z7XDo_dTdnUTtpT>ZC^m9!nj-%s2B1~pdob{(H}fh6fE(}&K|Q?TS7rM$3Z?yMTp9& z(?u4SqRSrF&Tap|^A-EdCVs^L|B8Nm6+1ZH-A&k?ot?$A89BLKd%YBQp_1lGC^xCj zL;ZrkOy+Mi+Q=wSX7JHo&wibIDb&)^!ILMc%$1-n;V{-U3f@Ev=Fm_{=4-Ph&>T2O z$V!5(Pi++u&jrY{SYd4}K2`RZ{Y{6xta{LTd+c58Q|GfsW)CCbWP)??v%Sy_L#u<_ z)DGF`y1Z}i;1jEA8Zx&w-RF|s>pUnqfNOr`cJ3H?NAIq58qr4O(@$ya#b5C2pTkQ6g7BQ32P}K<-8_UbfwQLNNI-o{tQ%=Geo`*9J37 z`Ogx2J++&gk!R>R@3e=pVg<9)ed^Dnbt`HH-R>98{Y3DNJ_sM{9*zK)`g~Wh53b5b zmvg2s0?p_zCOEP_@0&Ju)l>U45hFo`qNua0s1Q!A$=IBw)jG&W?@YuYixs-c5}I6* zXUR^9K(_;;C}Os6udzn>J7g=S`N$6bW7BXe+k?$!4*7BaxOMIHy6N$?w-L_aya59O z-+4aCf;x8AiL#Bj8}3C%_rGi(o4C-qgOAXUEpuB)xA%JDz-M$u62(zq4UiZ(m_j=v zAqg=G_MevqMDZwOAugs&vfs}^CLJ3&p651_64jC=Tdi+Y#?+KgS`X*u&hRBJ{&nLR zl|_)}_cZr(&)YulACy_EJ6_1*NOQ4~rN_-;of_X@fw zpT_i~p(tT@3X4w7c`PohEfEjTwBcl_;+>~@aTVAW+L&pvSs+pHyJW<;y*Vs2Bd6Gs zks76>aiW+OVAB59(DZd{!-fVU{EOxm$9=>3*YDkxZZ9!Ns6%}lB68TTXFoaiH&f>dK75>i(j=T6Xd>&Q z8@fuiw~h?AZz^AP-6!t7d)>Ef-}!#%ShG8fR0#$%fz9$T{3(mzOP$_lh)(gJ&me)d zLM_6~jBNA@6A+oXepiAUfRDm9fR>=`v6|l)gsI!YLZ4JjYqm@(OQ_%3_8kpNnTk8pY#&&n+yo z<{qB=fWw{tcR&A*51G80<`t8N#rN^mIDYIVGkZAh^W7Y`o4?37-_M{lb2Bh48_v*c z?Sia@NF}6)a1 zZ*qif_U`L<@azEpmSvQBN60I1+Sn7z42#cJ zGC`{OmupJc+Q|ydzQ-VC11UeJtIZ;h&m{|A4xViZgZ=ixR_o=6z zF{~{cGLrhC&p#gmnZlNchPRw9~a;m}|b0z@bY@6zs4u3x9Z!DAL={s$f_4L%ZnRV!VaQwj5^Z7xNIKF)P zl6PD#&o2vLUQk>d)|<|8U4l%xMsR`Pu|m`%#lRaB7X#wrvjO_b%ODHK(gF8?yO9|! zR7CoC2$k!>YGdJ8oQ^Z_p9C4(vknKVdvpLjN)_yYc1dY+q<^|Bf2gSirkiBUniv`v z8A;F%nSaq9Kg7RQ@TsA#l5H9-E@o0|?%R0Xj^$M$y;GO>UUA9Y+j4S_9{AJ6U)1$p ze6fT7lt0-YKmxmE5=l4#c+K%AYI9A>k2!YZ0X5kLeQ*>$g=n)=ZBT$dVS>j- z8&?TeE`rtBG`oUAcA4|_dlbMc=ZGtWd6W`B&EU*Def6(ga0neV?hzQ1CIiwDhBr-j2 zx98uwu?hrBy4;_A=H-pJUoRBq+9v~nsKyRaJ4={q4O0sB%qYKv$6$zxMFH9ArZzwh zrFdJWE;z*RZzEMP9`PMmzP= z*~)0AGc$9Va4+%y&i5~Ls_-8o_qWF=ZsrmaZmO3u6={ohRlR$ zFETyaHR&8#*ZLSy+Kt9|cP!z?+>hz(GxlYg(IVI$)n3~p1GdM5sJ)Frc4-PK6X1K( zNHP5<7`I}Q1O!ty{x1$V663c~G#7vthWn0^NEd$_De0FF1>VW>(;HC@HpXa7#?nLHx4(t8{BF)ClOvZHXifcscuMw%oJmwbY>pn zH_>RdM`J1I5D!ll$Fo71tENh)347RcFM5lwoO$r?o)hd}Hu1G%{8Q#>pcOzaSCNB! z4gj&2Zgsm4dZrBSDyNA!hu(cx(9aUYjUEAhEyf%LWDlz$$U~52rr=EpaXb(w96m23 z=9QAjxAU!>H?j{y8NfF4`7!>p^q6zIwg-izE3_%5wT|=0%H=r=3Rx0{UtEE`}l*=1o7>*LeO{A{Fi2=*sUjJ0>VvmbSXd4*hdI*bZx-oY5bQOE#82E3Ue;;SWF|- zF%b+KEIGxwL>7bYg^RV@FT$GA)MTlyi6Aq-pPanz3=*lBa%zfq*XnxMX$MO_tm=4a z1=*1h7Dg-V&<{PFY~imU`JPOdtIJhb=pv0*U)|Z=-{K5fjZ_ibR|ipj86d<3gE8CKkppl0l5BFuor)~V_ngRHV6HuH# z_3{Bq&hn9kqaXhW-_lJ&cF`}u6aSIKAZjB|XGVCK+$l%pE3-xcoIvj>#PADr{=x?{uw63>SV)_m0|imms!c1KkOcpy*uaORs3cH9%IvS`#>5_#w#5 z%id?Ow!XP@`?3q(w>~hjIeTsNoqyq9wbx!`(5+4C?tj62W5dg=qBV3s`{gX3+RwkP zTT3I6ot=Bh7tfqmF_G&Wz4b2gsI#{$ZRs)RNcnhri?gN4x!SW)HI_4?LDa!(w}^Hq zonsX3waRF=FW7|2odvXZ+7O^ztPyZFU-geyJe}z!HE`CL-h>>DTC2Bz7^BnbGjl_i z)!Ni5F1BNP#KG2?=7SOEHvB>U{mj2TE9Qj@nVbB$$oll6_)&w|m{K04188GEq(n)! zTPc-b7U~TgKR)Bwx^jm9<&?XBWBZJ=yW2Tv8S5QJXvP~N08PaK%ceclV-O$CLgu0n zA+}TJ5|#{>8eQn`?gZz!F!l-cTC9aT~E*TN;v6zjBWu`6s!uSdMl{LAHy$|^Tn%2Di16u+DEZHTE`q<_9>G2k|Z0*s|eIq9G z6G69g`ppW>=9JOT&funB|1!RE>$UxbCxUuH%VX_n69oYQ#()6(CLJ57Rax~Rk!m+< z&23QkC^K4$3gyEdzyAK({`&fU>=ljmJ|_6W1`(|e5PIbRCZ&mS^Xz8H*(^6)FqMI4 z+R$R}I3kpFpq2Mgj=NsY#coSMJ3yUn)9aTvuBzHl3KQ;xr=o@bTuX)8^z!AXOe1<) zHOGHRXEgx;ew`zCRQylh?CRbfJ=YT}A}}#doh%SZm90+xN7YclfR5BU-+Buk55>P8 z7GxR46)Zw06cI< z{2kE-+@eXF-C&sI|4q2BkZ}2x(nDdIVH%C>>Z*1!Q%=vD-Z!R+$7hz|VimVlDV0^H zC>&uXU4x&0PD4MSv1ruQQ$LNvIcJLYr;sS<%?y+Xaf>;bdp0kbdH#O^b^Y=*`^xN# zFBX~_zO39?uU9GK`9#*(Vm`n!OFZrU$eb8LJYYk6d$`-Z)ipHaB0mn7ZP^_Y9_B6^ zhPV8)awHYezNVpn{A0+g8y?1Nu+w}DE|t#tB$Sf{Sq1onf?>h}qw%i}>RaGr0UBEz z1(Gmbt1J)gJg{=F@ooN5gVG2#X6n2gc`81Y$j;$e@)-pYkUc{7ojPaE94z9WCiP}{ zUszsYe{gd@xu?|U@;YTy=LQFYC1gjlK%ds{RIX6SYzrZUMaavk^s!d zAlib_)M)}Ozy;As1x!&O0>lLuRF>c!6c++8B^Ur_i4hRLlJzCHUlME$#@+IAtJ$1Z zTN9RBWas#|l$-SWxaFqJe8)=uLqT~@u3ULvVIc`AD*A|JkD=>CM6>6iY^yaus#*(c z#m)pFJ*|s*Ay%iG_6%#*7LMphr|YY)Xy83Sm@RNWdN5ELr+8^BY!skpHYfvRe1py* zpI|*vr^MS%iT+IRXqdlbuFvv3HJP=ZyiS-?7qCQiQYI<=Vru6WvXd_Jx%~4iDc^+J zkf+J(S6y}0(=OMSPUn|je#wu!T*J=FNpAouYhVde;pn785Y3$d2d;!j{EIHx*M z3i_rlVZ!-hOG1z+qG4D>Xm=E!MuTHzU@qLW%o3KuWu?U#EnHK8reS05`p{Bq_JBe% z5jZ=%nru6DkR|*0M_A&RrRRLgY)Q<>HOFL!Ul4TJ0okG9zP{D|%<;ODMl@m=F)&1{ zPx)5SloK|XR$Vw08L8`yiOp~CKJ`<7|BC+d@_w?sPnc1tSQEmK&~&s4Z|}sdj_t*| zQ`z!3aEAHoE{W0vAO;$M;degJQUXFb!-IVwy*Hg_SEu)q{WlZOf#65a31Tw$Oce7+ z9sFn7VPwj#ud8F!{KaHEkUuz!+@fLN1V@-*&yjVi(L%Su8(9>6!Yd`uK_Ahn$3>_V zqG$nl5<5R<*ih}H!w(^v4U2PTbA>>o7wA!lKZ+qLuCibA^=sOmF3+SeAc=A4j&a zpyC3}=J-eHFDf|V&$-W|I_*3+cbR7`H<~`If+6_j zmlXV=a_LzC3s=Ds6tZOzO$E+CGc;9NNFE$LS_zmx$oMb{s5-N$;Rr0-+>%K}# zUT4%~HreXqvKodH;;f2Z-LkJece4N9#4qXMKk>zF3KRs@%j5}ORZ&5{`J`vEEVZb3 zq#}Rj!bZn30K6G4*BXyghp3H-jGT@sBzk+oOCW_rY!PI*INMj~jUaHkXxmcpBmv3} zmyb$T8asgnb!JMIWSGxo$*fkT4Dt6LE~{>>Z%i*fC#g3f%E>ahpIlyz4dLHW&XAhJ zGkjGS|FM36Zi~ag1o!sx|NEv2Lro+B0YPKRZAChRR#Wv&N=Hps;pUj!lyXaNFL{ep zj#qJ9)p#Xo_H0!GJ0eRZw3z{zvbsw>g7Zyiu(8t??cGF+%`zy^Lze3h%cbU(xzk>nlko*dO|Xl^ctLCQw0o{+r|{e)W%CGku{NbsC=;WLO@% z$-rOS#(yZtK5W^wT_x))Hf<@L%;|S_nJi-~Y$47rCwcAMb7QUH&Z^M?&l_SMl8;2?SBzcDXes`(Up!X{ z7S%Rtmaaz(XX~H-G~2~3TgH943L(!?Z^-j+ZD5^StGRa1d;R^Z`fHj;>8Oxf0JcFA)%oK+P0MV2$Ef!=2#MIx>eg}=UAy$tyq$r zq*7#TD^$u!)1QOJgO-O4Mh7XBaoZHS=#Y_UzCdbLhe<*d5|yXyU&`ghY|TVj*`(PN zC6{Zav?hHG$xoX|sJj=)dX-L*d~ioMTK1LptzF+=efuw1x%ESPqO|&(ce?lRKkbgb zxXNE53F~uf-&VWJ`VFMQn_>idj0HWuj5RU)m1CxqfWyKWfgD664%S*M_SOJzgNEeb zZxvwLY^135yo`rkkENg;tWlVb;7=`zY!@^=28l}N)1)x@?3_ifuqA~Ndh`D+tO zb60#|dF5$WZfi<)QntmV(CQh^8M1baeKLhpSjIEZGWD@K!s|O~+2xa!mCrg!CD{Xn zgFH!`7FUq;sFQy+Ua1)BS(-hbn78U^Yi{K#uH#{6wB3FqskGd+R5_kIiurEt@4kbC zkpkNldoELZLY#>zMlWb&#v2XA2BW?Wb`@1`xHK_V<+GPWv+m3oDYi;_im&b_=l||^_uhN- zVdC%))Q+hG7&mYJf%eUjY22D1|Hu!W1qIHn^A&ZVfSWyneM?5APA)WosI7$n(&-S4 zfQbP#`~Og-eTFx-z2Jw-tZI$Lg4Wh%t1*lWO%5UjH~cUiJKAg>&Knw1tehMiT%q`C zCI69#l&xIJ9Ct3k{BGVHYq7AsLBvvi#T9>GDo~%d1n@*PEo(I`Kyu~u9C!TzguUN^yWt>Fxbwfu7!0yVc>3i|yNDb=?K{rLLAJS9+eAo!)JR3KwpVD0HRL} z^l||UF`HK>t>(WM%Nuuz2QnNHk(8PYsFDj}FJrGC$>WPk>1@SF?$w$f4DD5{V_j`6 zd5-`7U0SqwNc@y%Esji{=W9$)W1TD^vLFTdzW3&vd*A#S0k?KU&%fjUt{y2F)RA%L z=bvLg0))KdXFzMzS561E)C#Rk^U>zzV$f^!S&2TIaNbVQmPHUB)N1zOZ7>f57D4=E z!mo*(*R-DF|H`c=&maK@XU<=1)#7Gdf~m<0iA1(ED7)-n_@5o^Z--x7n!cW|?BqYx zxvBZv*-1tnj-L?(e49Ch#Gic_H>lNOgt}Ji^pDh*u$&`l4&-S!-D5(tNxy zyr3ZUnB|1w$%`+X0h0VN2Y{u}-jR^p6Te=gv0pVAMXN^afIV=~My;06leHD0kzUZi~M8)5adoIxX4Qr@*P+P0}I_;KQJ~9Rb1_l^2R7G(Xvp;fn%;vcFm|qdOPpa+5NMwta$m4p` zGcs&__UydM@|=Q*0eeAiNir|fjFcg$zd|H`(0>?oB+bPXdt8Zn5}2xn`a@t_iW2A2 z)JK3Dculi&DKtslqtkW;mcwU1_^gLBh+vX`FK~wAvNAOq`QFREGKE%!lk(2gK(01?Fw$ z$HbTXF)|>&I`l6-k0WLvQ}3WIf%9-*)bU>mChW|zWzpxJ>#T){Aaqi8Q4wDC;{|G% zBF1p6b&{GCI;`V!4zp%>yVq!4{x3o`FD_sU=$on-IPRym)Woo(9*8J%a-k zB|=t0+h)jdTGk?>gp~Eeh(QqEIg1He66cl#xfF`d{2Bf$*%DP*Y)WI}3hgG{U5$-T zWp?bFd7QtpQ{i+fs)G54+xU~B3y&;-m+dz0(i{NeVRzi&EDeot5SgR7*}?zlsLL1` zagvWKxtyLHE}Z{ArI4866I^0m2F1;buw%8DQ6pyBU^ggW4h1Uk9*)#5fQge09Ygz6 zh!~Ktx3JyC1VvcENOV1&#dyZW?baJ&w`=q*qf7Ib_D9^jj9FWoY)D8;A|I=7*?o`g zHunDG?5lX5*@c)|<}E@lBj2{%>waMG$X^;z7b=UlX_5!cin7#oEfZ6Nf%1L~xsOTS zINBcCpIE4x>M7|h?y0K-E>PcF!d(Q{i{D@#?s41THd1e%+Ceu8*i0GnY-l%|Rp{ak zd`6&2TmThOKAgfQr=fusmy`;DDWBuz6;YHHuo~E2A4yPv?{vQA775O3d(_Yt^A>lJ zEqoi02Y*GlA-&6M1x?nh%H7MpsSBErDv9i}s@X90`o_BW9QW+xs#T2`A2Qj^H8gPK zuw3yzJ{uZI0=QvT>K_r7n1gSAUnLJxcJL31rcobEqw@3uXIflhvUBN@k)W#F{^pw2 z){{ltmSnE*yrLBDyh1J+>yd=MHk7UnK;y;E<}?>XaLXv}IuGsx<|@gy>BRmO^ysra z7S_m1HWzue%Gw=k-n|n!C)HKQMWG)nCXiVss8Rs${T@ay!0kxpf&SDyGH zz?tAT(P*IKGv;1VXoLDDRL@YLp8gXF8*m$9@S^--M8M;WcyPTJ{k}jvk|s@h@pw!_ ze6%vnR#demvOOcsl$zXW%QotR#{vRKEheNgQYjN2o|KVgiHo;Mq*9`g@qba0K&6uZ zS?Ou=;)+wPtrAR!)7jn;ho8}d$1~d8>zF7g&9CXLYOWei%}Ot+$Q-goM&wE?O3Zn^ zY(rvL7HGE#rrJNZryiWy!e2lpy^sE$a@%xl`jX=A?&ADR`?RgR zheY)h;nu@jN1;$fFq(a@sKOCm`{pQ6#04QB!NJf*{f8{uU-P)}CjQZ;b&7%( z48w_>vU2Mh9sJ9IY~*w#D{Uf=H8t@=v`Z3yyW-{2U;PYDgyPm$Rmr)F?r=EnxQILM zxNa|Z{5ZGwIuwNLxYKd2b8u?;YeegCbUJ!Dm>r%UlrRPXz>3D8#4i&u32~-bEVQiX z7kKTQTpE*^Ls%hgDZf#LicU{+aTsAxhj?)y1^k>WgXh)4aqAl#vz+HDZq`w4T5nvx zY0NbFGueCUTN$G8elBox{!{S1kE(q_nwa zb^X%wJRw6v{|{r|0oYb`_3!&+t*7y{CCi$YWO;9S?;YFmUa=KhwiQR-j(40%0wDIOE`P%$+5=<&bs0^!DFLA~zdaK5VGbajOF!U_`{n>{n3W5O*O zSvc~KBME}121&Rji?JM&aq%^$IAS@6)I%IRazw$H6aPJ(`UV*Ob6;|U#=ayqk;sHU zf<)H=lIZ#^jS}&5G+K?h31oOXlmXmKAD=?bsR!^M@OeV8Z9YZQr{-RO%HkE<4X^8x=522Zdj0ycX7y+u*^IF8dybT`tYfW3A&QxB@S! z+BRzv3sA*%6)3zV+M(A+0#Cl0&Y*+EbcD``ii_z4S|VV$XYJ;mb*}Pn9BI0|{QML9 zh6hT9(BlL$3Fftlc=UP!ibRUNd;hqVrnf>bi`9JizaaGg;6E~pR{TN*`GrqaaKv;F zZ*e4Q;>R9gaoRHQ$=kx#5$mv|BZ(kD=y?&9kl+ohTd+a^vIxC8B+~G^q~8RLp=NiU zBHIq3{H5_1&ln;T4|(H#ZpsjoY(Rr++SM z|1g4#`1&45lB{e~4#$5fk^xE5@}ROH+%}znDkghJ$9i)al#H(cE&$}~_TwMXsG2Pe zeLetuhkJ}-vC3vG6pzX2jS}g(FYJ!hf${Mz=^CwkK3vw{U(uhH z)&Ga)T3LrA3rZOx-1%KjyCbTrII6L+D^&=DKf)v7IE*&PYH=P-8<_(Qxt~ziWjM*4 zequ>wOVY4pa;X(3;et+J6YvPM8(AF!PZKzTbrHeJlGG*8?;$2N{xJ}xGPWkJrEd}o zHVTDb;6KsgHz0t7ZZ|91Z}KnW0O4cfDDzn-a5q!ZhMabBgkB-05p9tQ;))nlHSnR1 z9#1drii~pWy3`CrrC&rDB6tF69cjwhV6zP>$+TQoPfsH0G>o+SDSie8M3=q!Yx?+< zw3bi6xr#WYdJ8lKBlxu&@-L*v4`JHGL)5xlUBGEHPkJU$BZ2)0`i=GUL9>VK4yOoV z!$}f`0K!Y!Pk3pa&9`w36W7>^P%G|kZf?9+L zr_s3f)uK`fy6)X?&(zP#k9JH~-Wsv7VR%N;ZORDhSIA%ay z9o1V4z!|Ejy<6Jr@pop7S5N%%HDCQ`Zj0Yjb#9=!d4RmdfM?;8k7NN@+Q7Q!9LJFK zn~=Z`L7gMV9ESu6jAH}@bQ%AI?_8YigS!ANM>sDqP7N9!t;By>$rteY8*!NcNd)f- zk*rkEWZxBeAiv;{vBDj#JSluOo8kLo6a#z#lJ2-W_=-M%IL?&R{9Q;kbXS8Y@;-r3)u z72+*%oa7&Y&!lDK7KaKzy&Dr)03hE1(#8u2V;SVVb0c;ywG*-4Vo1m%PvlNScR z{~`D5j48gmoL6s)8IPH!XT}kbh5B&aWpIbdtFsMacT;`8pyAQFm|cZu>OhmdF!kkzGB1$*38DF*PiEH3(lkaiH*Z8Inh=t|w=EqMZC+}z9aV`hP zj)mY@Ru4YH)=H3C!hFD_m%IeP0$3V=U8h&Wg)h?%;cLL{(p?wVb6;!qkiHguXq|>*-IKr zewZI8SF}giqUH4hsYo1=DHqmO(@{dOx*d_E>@ zi;q;A(fIcMwtk<_TTrtt_}4;)IR<@R3((0nCtjyqpXpblzQB9$5xg(@`+ckqB4@J% zqRZ8Q`2DY_5kKVTlN=$SnV@nwkZ2hqA~>!ad?z@fo^)b!SZ|8d7F>E%TOmM(Obi~5 zf)03E8!Cqup~-n);cGsPfu4piHgDakdP7NpQz(Kyhd*_mG2IA0lW5MK>rI5#IP`Bi z@Q21Rh&*R%YG5)FRDay$BJ*!uH-S(2rnFs#|KN*Ls8n&KtwR*0fOf?wCLvaS~%{|rv`Z*1!7+_sKe7L~hftY(K zA;)H9cv&n4;U+6`5oiW3BCr>M36+@z4g`TFSQJ7{JV%I^;1}2=X2|FAT&B?62M^*` zue}!C0n{nHk8_vNj6)CPVm>9mmHut10J1PTLtJ5lxhB;hpiZi8#BU=xlF3mxUx2?9 z@WUfcs-I?Vp?_T!5fPu4XUxunkIjqptTGq=%5@nv2yMVb7n z!AepXzocIX&*gULv&7YK<^V3amW@F|3;{qW!HTc&oI$>bEeEX+rVvym_sPQ&0^w=E z_k+(NOe0j!!Y(Aq4uxViGg=Dds814x6L9OWa~LfqbVu%P4CMH(nc2Vp7s1czU+tkK zX_0lw1N3p3=vMlSWSpXZ@sU7+6v6Hie)70OLW7gJ2Rv7^DaN6X)IqkssEDyM)b7pj zhQG_r^4IR|uWWDkj(WYL?INo`t;lPR^j7%;WnP2Ld*|65fq?fplm#yVuM{kdi!@S{ zpj!w5T=ee3nvbB{`~qMKOZ7&i+8_yPdI8KOnG~KcOk2xF&UWj11WHDLEWqE|`Ib|t0fTcw3k(f6*q!PGB(j^~cB`ZGC<&=xXSD+ot z05f%&tKL;~FO|Ry@XChu*;(U}<4$sm157OMKDybs!O*FjM%Ptm^E577i+`gXA|+_;?W6}OyxJYy1GHTCfrb03BGVlcGh=h5CpoO6wPWS|tKhBw z@9wOOxW`J0AvDJAT*2^CUW)h{$5>y3%sS_CQ+=e>-}HaGPxSq+jM=$J z7$bTk%7;R6KNFlLGwG}-XJ#g@w6n7`Hpw*PXladUb+<;t8$86cHbzcpd6+khLT|Tl{x5a(sL!eNDhf>3GeeLTZ_Tv*9of?a4!Uq9&d$5w^BAPt^Qk9Gok7~ zKG7$v;BXm_%b=d8gJ)=}-UDqU*k%&QSB>x%21fIJ&Df^(`e<}8T5o_M0ZDP@1e-!4 zMN0f*XgnU%9q1OPN37N9^=pIQHsa4U1D%oq=qpFv>&2f{P*g<|ap0i%Mi0>OJURwD ztb+=cH@ime~g zpUr&n#cTykm4(jpOCMl0{|Sckt0Mf>Qp){cbBejolG)Xr>gcy8G$UY1#k3|Sw#I;0 zJP;Q*A0NK}?23nukfoFm z^`8ucLomEh+6j)@l1C5B0~WizaNG%60On(fXLXs=ZW=BgPJUDJj7;_njczwZCL3iU zt3kECXnlgw;_|{FRVvLzs)yu^%sCs)DC?=PXH;8ED)Q~%krL|fy0%r4*2$Wh+uubG zrZCK&Byd}jzJ4nOno1#2Xa2iKXvb-C7=#dq<;wqgVrp>bqJQQgq;KfUg(5!uEl7Ko zda$Z50yPYO`spwmPm`K&a@Hl#J8dp(Ygm|?R7Qb5B(5o+WJ_4j4Rj;BAZ&F9YcE6U zfPmiVZXr>W%O`*>>VP?#;MKAYF9Rkblcf1M3rb7~@#6?{2NL5BK^D~7aOq)B`YT9u zGfm$tf_c`N_;a&Nnu*kzzZ0d_B?J;kS@=q`TA;t0Q2=2*qSz)44}VGoNtd!O#c0$8 z}bTPiD4z$Yel=P6iUUYHyr+%$+2+ryxu2f-p_amR_!B6yspxWK*U zFeRc;VSWv%CiLiFr0NA>TTbV}_byIPU1V&i>qZN6vPzrFD?JmVr^G*Op=p1j*3TEF zjvhb$EPnooYJXg1^Q=VbT1b!)Uk05E`5r#B9)P8LD)RkF+cF?H0pcnP!y| z4{Y4nS*_KsX;7<^lRK+B(K~zN>f93fDus5#2mbNa^;11>A~W}T0eJ8-kV_7Jypq@_ z4zokf;YILsxjG0Bn{nv|Uw0PP=n}jmT8~(}OK$l>KtT`?kWN5A;G1Ry?kww_xYM{2 zOrU=+O$7@DV*opn)n)?=7@K-&pfwo7z@&v(Y>!qjylV8cn^bGN@i_O0`tci56af|R zpGc9VcSwKVjr;D@`1)2QUzt+vE5z+0^er*biEFc^((JynzHELtPx@*z{#-o>47u_8 zdi0wx1p{^oa94$^wF;pOmC|v*0mhqqcnl`Uw9BMYS#V6{=Vhjd{i;NdGt!aKN}0?6 zgb1WADaj2(k%LS6jBGdo*m5m_4?hgmFSwvKgY-Qhnk^C{(@^e$#0Q)m0>4)W>jm)@ z6sd!ngWj#=Ag-X$ftCw7zf11#h3qj|`w-*xvZ@+h_LadCqIvlwvr9gejNd^Fq6wi8 zKqZ7+8a>U7z^Lx5j>cksaoK)NcTBtp2EOQYkbPXR6F1Mk@y+0eqTejyqDK6YdTfoj z9~jqEX-`i^Mh4yzp->D(kk^a&dAE|HD4wE(UJEHMfmpz=*xa6x8M$5^VN3#==210`mtbO}d-twB>jFkC?X-= zAQJASM>8iAn(;qq)Czzx$QusJPNM0g;e3dk($Z)u3Qo4dSg~d?2U?UPsmGVs-=F6z z(zi$*jYdd`AK?fnM;schvC%AP4X>B*ph8PK;2mgM-=){t4NRGn9*m@2lTM?6q9$E* zB=m?*fPP-2CoN%5RIuEqp|CMn&z-QIA)JH!@ft zHcA8R3J@QH1i%WDz9$4iTd4!#CDOTPlcfX?>mudHy8+tOtevSaM5IJUMMOr(MdG%h zHh;y;4fL^7^z-m}Xe~HLc{c*_w6dlKYN41s{hzUr5lZ7n;$&j@?bnBz+n|yIpIa(` zsH#G*8sL{~m0u?tO%&oci^9R!NcC4;brtef+N$l<*6PSe_*EVFbq>c6c-;TABYBsC zvHp*j!GAtoz1Z(tXn6#sz^lXttKcb7jM{ohO#~nMt{&q(3Tw;Ft860k(Lf)9=fwr2 zz&awq1{bOTvnCA&7c4I`5EqE!MM$qC%L0yyP(V=qXpRjKV&8z>2D^OGiczl=B2;)Y z%*jFpaWLUVE zMU`J^npe{uP$q)1P{FZ$3g%=TD?oM9At}70vR>R>;&cFE!%v6vV$fcAnXW+ZK!0yw z4ZL>nBPzW&FE4M=@6QR&DAwlyD+a$2*usj2jR;m32Y&3*DoO;ZzoO(sKsIupbU`Uvc$i9w%wlJLLcv+kX9z4msr zdcx%@PVVeX&Wf=Pn_{{ohQ^JJFk=i5IZhH z*h!x881#~G!On}>o-&2N_$S#|Hq*lc(awSoeYz03&=R>{E@;RiSyck$u;>k|iAmTA z{~$Uc1pwe3fcXyrWpD;SdMb8m6$kYDzn(yXcQi$QS&KOt5P=aTC>x9w12w)sCnm_v(U6?8u5c^W$5p^W*Gi4 z1yz<=9f7uCG?vtp^t+_qq|$q@>RRoIFbjP`sDluw`_?m5^v0T+{9$KCR)@78mQJOd zn!;Z-<&D-3y!`S&?Py+;H#gVoxCmtzL;U(E+q<9ChDU~~KnQ<@uvaeeqlh&xLR{xX zU`4TKg3H$~dkCyz3H90p<-%%UuvV9Q>jb0vmn60X^j5&INe;OCzw2UTy5_ z>kCE!e{&n*aDsDvJ@d#T@CpAzF_gZdv=RC~5r6w7I5R9J9||f6WDx=eiN#=zb$S|S z>p=rv{iohrN4G;Hr}6uZCr|BI?;Cpc z*2??iS`+FTbJJzba$% z>Q`>_Sp&j*bZhyRo3AL?f}^(-Zb7eQ()3SLHU4{`g*WTAkQUy9v0I}3s0=?xp|S6P zyEM+S$MCoL`PBCOeEcnObEbYu(^*=-=J8(ghKlK0y0<>KzP&x6J+8MYyB$^3*IVi> z`B&$|yQTh2FFXN2O+M`L^(=Q&!ZMVG2#$vY7f?GF#j(uUGfpz0Zs2Y~1Vz9~Jp`iZ zu& zr^c$J0)sr`6v1HNQ?Y1JdiTy8r8QEaNW{Mz-r1L%2Hgsv=6qK0uD+#=UOnvbr0{kM zBVrz>kGGVBkZ*KR&BK;cQ&&9Fr1-r7CC=Zh`uB zg?u-njg>_5IPN7mUo4s)a;8BdFW70Y<*Yv99BSgdKzkteC{p}P#P>X(t#I1(QN;z6 zq)SW*B1}cdo}R0jU0vxTvbNMTgb3iYE-5j|v_Tu2sri=}Rb?Z26!sKnieYeNW{%94 zhhMPEq&`_TOmFO}uI_>n-NTPQO1^sTx#41X4m^|EBn8YcS|jfT;5t^WNH!RyUonF9 z3YUd(!OTW@clibK)Y52cbaD^MGgzvhu_8&WT7s-FY9ppDMJ_W|#n2_y#`3B1h-#ay zIs(2hR-drKQ##LokR)ig^&y=y91OUHJo;hK#R?2Xl0ES^YKj?>gjy#QC7T=pb$ zCmrg%pu>p}_L6fPVIP-_A{+wPUx5%w5$yv+kbI&#YfcxjGCl<{V}&`Mkx{vS&PREuS&EP} zmV7$tP4RYh!Kdo#RWn|1*>~TO(8J^LQF2MNEL^$UXcD5@u3Cg@(^tojxU-Wxw&tPy z`FupyhREn+Y@Xc|W`q1Osl+T}ec`1Q;U0l_2@=Tq2A8u(A)`o8Di*0)q36AV>c@Xd32L&E*{tsVn1lh?%udwUWd7pZ`bkThtEX^ycsr)Cz!sUqi~N}BbFm*i$O_`_>S z981tUIBP8SzEszL$yw!;AtwpscEOJ#E+xdS%V!vtidA+l_!%5R2qD0VfFcD~fP`ol zA^1xc^-h8+IVj3%t}4oM4a=#QiYS{;+nT@188D>e znDIYE$djy6%G@|%@unwl+AjQ5HxOZ&TCi)>0p|NKg7n)KhmRgb1{g`JB`3MHM896! zQ<7osA$VO+9736SGP*_Pvc>6mjm?o#VR4M4B_V%w+v@tJbv>@A)Ehmnio~?+#Bz7` zFXHrSl&VqNi_@VVw;BMdIZH7sbFt8WgsTZ5z43a()`v}mZTsK-C_aZekyygR&tNU) z^6D(Jww4J1!}L4!Ej8YjR?%RpT)< z$Jg;A47x#!yWP}mYBl6nfMh_OPDPx|Y?j3>{V(6p3O{bwDd;JaY20K&kG8kR2KOr1 zMv&!Pr8_KeSjP9yvseRAJTII3nvBYV0vfHtK)&3$fUdyX+|WUGA%G$huUDixJ*uK{%SO%eEK3@S#-H;+95(`2t9uci;YABY}`6mq?t z$y^W8=hP0qEWt3t)KJL|J_AIrgaJlow!y?U;)%om^F~ugsfnKk|4pBJo&MG0Z+?R{ z4_jWFo1^Z=pW%b>+A{%U!yg20Kd^8cy8XM&aK6LYKB}a)nslMJK;0!*8v;I#=Tb{S zyj}|2o@H7AoN!idz%8P3pJAA1P)g7w2AFP#7+t+UN_rN4AQvezwxy{6-;Jc^xqjB? zm}cf%zrSFxre+X#O)}|8{&8<_Lz#aA94g4!JPc2ROM^Skh1?X$7mz9m=<)=7G57}| zEfHIA!ib_?GUA-N0Vt78q!0wxb%_SLl-S67DhyiIuC8&L%*;8uto(M%cv4am%4`$~ zB_`08qRH=WY@!}-T4aJhWERoKD39D+Ha;Lmi;HLr-YH*dT5EJ1U}*J7c8*~}W4GtU zHa1>I3k6hoI3*C$U5fLKjhojp%v#jd)063IZuY^w@xwB|Kg;$!BKt}@t)|2ML0|>t z$`{q~1N|@5(~Ru(6l-|Z12(_H7=;APicdGaK(Ha7aJ z8K(N#ljop7P=h~18YJ%NN%u80kjsM{a4N)5q;8Rc>vKW&o81BRC0zzcUqRyEu1GF>SR?gX;~W#bjhUm~3!W8I)nzJ`E%*{gp``N5 zQ1G+_{XinYl@j#MFTn45KWbOhPo2BogPV#Jqa1v#7;R!V1O1?}akPzL+T^X@+en`R zOAWjVDocdU@)F6@U0vDP*;H-Swz@QDw9~aiH7{TuWr1(w z=?c{?x`?C*DhcbT^FQr>`gZ)o&Eq$;R&Oe;^j({1+cp1ASuxbVSiX0UpGkFr!$f?{ zWY~9N?PxR>k4B z_cY;hzO-_+@eStr=Nafpfq=qBK0msOIh4axqgWYl*poS+k1vdOtcyyCud!Qg)>yA4 zsad3`4*q3TE_1S)$$|R|ha4N>HblXlGJu`7fXcrF{e(MO9@ouzB_zoKM;Ug7yE(Sx z;ezh7FNw<1{ZgOfcm;YA*erDlbc_GE1pof%vwv3{+fkaIQLdXPmpativ{YM3TAF-5 zTu!YI29%75azWlfr?ugAvadiIH>e;-Uh(-D^~8G>%gaSUHW!=&aI2%#5`3rj>EJ|vP?*bD-dvet!l zidg#s`j=Fvuu;F3#F#9tf&CGfAh$N8C|HmcmJJ-R70br=xWHC{)WEVWr5s5d52S(> zB{J&JIpM%W0JS5GNYG1$$ zjEA%&Q9Dm-m^NC4nj#rxWO?1r2UXuZ`5<&>c>c;1PkxQ&%6j~7l;uJ-as}Q`|J=9` z`Bmh?xub~Lq&Rw1v6<;uTY!I})U>2Oo`U}&b=2EB;^Uw8G^F1bB>_{$zDvxK*kX}T zC@Ox=tyEO!L)A$&k0)?SrDt2gYC9i*>kyQy3=*l{s>^m6`D-OP=?X7x)JK`LURQNy zW04~LfojUy3hEe7(J<#(TB_GK&~ljso6U(*nHklF*Eu_RKR!$SwV@gGbjkNOLeEkg zSR^Z)Rd%Ix5H@73IFGCVf(W{u)U92Kc+68DjC*s8s_2ZItzTu`BGaJj$|aO)?Wwc) zVHZk4O{4o;E7PyfR1W8+ZY>+|j^`&8rso6?9;@3?%IXN>bG-U%zjDiQNR+$N&FmZ{~?eeelyT(n%L!KoT4 zle#UJ^gW|W8jzf?RA=G_2Xk0q92dzBq^ngT(}4du>#l0QIWb`-DnO&hx)esks_8e8 zN27W3H@)+to8~*;`kv@xeCwU-?EtvlmsJ~S|Iij?(4`z787ZGmD@?7hxYn(K8YFT* z`OwGaPM!hwR0e)U7+fppP#t#+fbR@G9Jyld9C0uWd-+S44LNe5=LpjU)Sj9o{DdKD zdsSNjzD1$Dp#a5|%4F`Iy@SXzdhV^l>f& zjivEl(foH0h|$=hA2m;86~z^_7gQzW;}hs)e3CtS?JA2SK7jvFXd6##T@y2EFDveJ zS!O-u1x;2{f+Ob^^l=~n84}o)7?8K;Am$+PXBBwfEn!o{`{ojD5G`KRE0_K|VgMIhgwl;NJou+DcbVRAw>T9iYB%4ajcKwt{dRv*QX1Z7`x9i3%Fi1>$ zOk=`V9;~NiGRk7ht1YuMWSL}AXjCe@CAHSlsh<_U*x2}aW8*Iv<|!z(Z)q zkJ{2T`3l*P$zn+|8wb^i(h9wPU5wpTZn0B^5w<<$$oO_#rpt;ZqGFBub&dFY{DQ|~ z11d(pGMti04M_2KrZ#y)^?qphAqXo3opPMjDH4chdH~e~&KbdlL!K0z+Le4lE(Zlj z9v~td4os$4Tn0D+WfYt*kgRkNFE~W-<%QEh*Z@m@7xm3g&oIoH=0p1*Klfk1pa4Idf z_TIPWr_7l%%q>o)O~u$Bbt^gyDZuVBbST^n+Zrsz#TIx#AHvJ@RWVFeAH1+bUytZS zyV?|??9`>Gbm2~i&SS?OrC85PlSBA8*lhpi(ay+%0(#cbH7uq#-d50b*k3tKlz zOfAt3%R2aSS%ZZN$z(cHAa3`QNI*^s5b5XLS6Adnkfv2;9%Rd=JD*_f=| z8xhgF1#-87T0CUC&cDi@V9txOF!5!!@v3@@(^48`QB8@Z3s6mh#OCzu?lcM?E(Js_ zeK6l=>B_uX^h^Vn(?fjjvGK%-{_64LB}`fxL*-cF7&$IZ&9obiXtXY^`unlTs6tbg zPBUP(TVhnY39~CQS)&-R$2ha?FjFd8r!CLONusYVWUe8tc+$Upv8P}vyU&*c*~4$R zzd#;xe9_mUhYFLKTc8Ch;1uz2zR!XWY+x-LtCFJK6j@UcB7(X>bVj8{4fmdO9`Bv> zjN_LbPxo|b@Eu|_v4}RH^GBxEwd`nV^Iw7SzD3`Lv1^CVpC=Sbcx@Nh>ldicS!)eS zny9;wPsF}9MCPmqX$ECSd16LdPdHDv52g9>leXvDIwJ5b60`-uxF8;dpF-PE$6{kq z!Irv$n!#5$EYwu5ni!UuZXFyvrG&qRKu;ZD?X+p>KAfOSNI5#Y$t&(9-5D6X&53_9 z5uLww&5qTZpRu1@H>ATS#Asv@tw(>labd3hs@oB2U;X!1h=EEl#s?cf+7PDoyJfh=iSLBJ=*E8*A;cx*84qBRJ z4gzbd!-o%#pfEfB`P4Z6u?^X3@I7<~h~Td_z6;Vf$DG)BoI%~|53b%mbmHE@jq7%< z{}R5xV+TCt%h#Fn=b6{ZHIRJu5t#RC1*=oUt^g}L4)(CaVsolZ%O`4(^AMJ;24VPw zED+cgvM(@TbwX8jO24v^QdDgx*)&*RKajuk3X&`C&eFBt7A7Uq?*j(Ld`lA zpBLNS%3RU7I(sHQ!&5&9eV56-Aht*28L*-8|My;oq!bi3$*MzGMUGu-!W!RB+jS!Y zxw+#rCu}Fzbvy838twXVZhvH5bhLVlGIHl4O2@CGG34r~s*9bAZ>Z@Ttf`NlO{*zu z4O~~0vMD|_wP|(MZMO|xM>dzt)jq~z9V8!ahwFjfQZD!){zfP}ynOb#p4;esd=#z2 zN2zF}$8RG4+fi@t0L07s^6+RXE)$`@(2JB{I%;O$jC%^t1*m=H3z^3cm5;@xXVeGM zO3+$-XEFFjaj>5RW62Jyg#HKsyAYfuBvM5E7J)ZSgo@}K@)24D*37Y}638I8(@Wk# zCXqWGkEC_r3n3_RfJXGxmn|C|_{sHy_?!d9R($5xX!x4&NTc&T{5Fj|_!#8e7tl87 zo*<3Ys#Mxo=$2(^5%L9eqr1nL6|VxWGLn4|llzWB@y@~{3Z4Kl*%sAA{-G^x6P1g= zIiH1wkQr;!dRDFKNi%324Gyg#1yyInR#QA<4U{B7Ws*=KTZOWuCh3n-qy`Bh>rQxU zq+dSiQx8r60@yZWewKv!LUjm35xHLB39+5Qxsik*7BAr|!uxJApY8qzDyr4Od4U35 z9h`e4V*YuL+v7@&mZMv$#Lz!->ZV83hYw~5Cq6-u#oGY!ePS6oT?@uQv&e?o#g z)$enR8Rz3OGGlReV^sDyEi()nWt3a4lWTQR^~6PoYmbATn`dC}h|Cau1lS}yos`w- z%WC1GebZ7$8s-{;8|E73&`j#kz-B9cWoiV!WJB@g_(2)~;TLFBvw%8aMBEm%^*(sp zQ(u^8UhF+_Z~J1)zQMl@PE5cj_V1^%Txci!{RqfAg=K+SC+(e=JjEr=3!HFPrY_YI zrxt5Bq2EED-;TAB9cA|5M>G|^_#^zUu`?0)t?`Hf{6f@w9rz0x^(y(BL^3r0%F`db zNm~30RRNVKJnGrO*49BZ(iJ^jbKl9@snpej2{~CwN~F~REXE5PI^6A*%{us#6bxsXM`JP0wgmN z@VYQWxSRYdM~iv;Ks0i+4xcT@7U=md%-yt5ZA)HE0)U?S!D8rxWdAL@Py3_+&$U zQgUAGT4PFIUUI4@fB1Jh)4WhfEXbi zKrGR%ok(WKKOLXI=WIyUiXWvrWJ#8nOQ+=u6qy6XUzHVcJ@hL;RDe!l;fW2n$n7Mpy?&ki!`n2spR2P* zeh62c;IQ1k{w)zoi;!&+>(C8v^*mz9z3K7080M~{{=C7G^|vf6oM9e%h(TA2&cpNL z9pcEr2(iE%kyZ{bc0OE1Pp5VI-Kn4iI?|`r*JUwtF-+Fs;`7WE_d;qVE7;B%aPm1sjEEJRI80y3xg@-d6};Olup|WZV(oG0##zw=siPE7&1KNJ z@#}7#!Jv#amv9|AN)Fk6=ri7>lf9ZcF=bO=W z|3>aGoj#@~7TT`qV|!wtRR#kCO5>To4B{7(Yz{=p2UL7AQx?Sz4ysFiQAzPGUy*Xe zIyY-0I4J4CbuOgatbhvwGEDj`*nu7PA9b`)O-?sUuLRCIH0*@K4Rah?=30WE&9%-^ zGq_ji-(tCK`4ivwVpO9O?GUTbE%>v6*R0lqFP&nTQ&SzeN%70LFoAO=!~Lu9hM_Lzv9lmE4M7vf4x^5!S-Wl~uxmuk>kn;lD&Z-@QYlzPI=VYb=irg5jx+RBl$O z!7JJDjIs0hKwes*f9ovT{@o3bTV~sw&7wG{8+j(V*y}A$G#Uooo!+>Vbho$NJ*Zlh zTi=kAl7c-@(cw)^^}^D^^}4^`&&mghpCMmLEF&TKg3xkT$eL|(ApgIzlAZiA)!rK@6#vPZwKKw;rJYl zT6nyT6a}{a79>LD_^4d?07A%!Kd>>+s;#SbhnICA2M=v3PF&2cjnhjF-hA6w+!gc5 zerriLt(#3N^Jz6+oubENi_!G(?Oqd%b@XLKsdSbuM_K~I!2KpbZ6?W8Ss_o=NcM;L z2PBFG23Cl*vm!(Q!ZV-Uq)JJ98~Pi(c7=3Z=O4NaLbmP zs2g&@Q%{^WrNpIX4X)n2yPMCG4MJB0tFMj0;enVLQ(9v64XGOv2M0?_2Y_P;K?D=exp zzr7l?Ymtne7fH}T{O|G85mjvq_$^`gb%V->>E{+u{KDyTHQ(B zEi+6Z8?wA-xo`1OD>her_p`-{MQU?jUcM$V2AT`ZqSVY0YguIE_hB@e zB!q*9x{%t6e+C?J*paAud<%{CEug(<)6d}ziC+$cBMIANUnG0DthcJcITPuQE-dwD zXZvx)qfcj#tVbJBN03^JAm^U{HTp?5&83(f^#QLMbNI}pO6 z(gj4uR(Qwk?GOqz&@<{LvnQ!(oASppQRmC0ii|+umoYc>%sVsVciMJ{hMJ(oEPiup z6hCK0=@l3lu%ioTA~^Q4!mu@=G;1|9gv z=sk|odb<%Hg&57QuQl7zBQ^F`duM7)`I?BxNPl#C=OAGeN1&v>r6E@gvz zd@wb25M^DJ;Wy@aQ=ql*x(a3pmYj`w30|azt0%MRNOvD7nC8O*XF(c~;5KB*K{JO8 z#|*+XVZ@^QLS{#%newZNBO!rTfNauPuKL|5!u`GJBe?&>i(n zmrUYkbjVzXpC+-^qw2IP=Ki5J?=GKmZFbK%R{u86c4hZB$P>3MMJ5LZ@W*JHx?yBw z`}PqS&EBI3r+R|3DF&@@N@{Ud>{LwAUH!3LhNwnu?DWP+v)(*YGcaS*YGV80eDfhb zB=<>pLuT-xd@M_Mxm%5c&7#6hu)6{BMTCfji~x@&XM*_eA!m{RXPjfm;)EewZ5f?q z;ek-@6ykj?j~DzO=uz`82zX(R#A;!3%6fIV!6aBk zS_DFol9}$+FiO=wx}i4H^>27x8`V{bMnzUz@w{SGT-YTrK_vBWr(S34E-Wm3#%3dH zV}{v)#Ba!iQ&$PKI-MW|u%Q>VN(@nE*af=OfxF;R0GXL3(<~MAaylHeIhdzR7o1t% zDwFe0{7GhB&`OFTZ9y`W#M5WMcN3CY(=&qHX7htRzoSvPlAecdhyheQh_P7=5{ftO8Aw@&lGmjKuE_3b*kQTNJ6Sr8&sdP52|obc zc^846Ju{p!>F_2m&_5hLdv@)n`F%*6Y>b#fW(=}86=6(1soF*f&_I7b!gMlZ3ua^{ zN97L14BgQmAK#Bu6K$E%O_tcY?c3|Lqv!__hGe`Qtv4a)MtcNpPc}r9!a1Pe92{hM zTs9WzBNES^Y!G4+;8n4P)-JH3_?kQ_E8kE+$?(zPbrta~+ z4o18xH@6MKP@*^Ez%L>*q$0s$LB4n;yNOJlW1(bNS)%`!c)6f7;c8c0E0>ZKvZfBM zm>1r#(RM@s(BY`7+D4X`*OltH3q)I^hj6f_PPyIb=ymO9!BA>U$OVM0cgjdNdE5T2*YBmWx8v! zsLdpIG)?XFj~kP^42HPh^ly9enkk)7#@$;I;8G{y-jcw>1w;|8@E<}x9~3T=i|4?! zWmkhh#O^H4C1wxZf4p7FhZ57TvNc;Q)ik;m4k3~>QP$E7-*-)Nj_pUP`DsiJbX&h=F`B?o|? z{eJ(J`?pT$WpZwxNxuNX(My7ukmZsQOAhP6y08E3-co;Gc$Iz=cJp_Sy&1f9O|-4J zvth4uAV1nsqFUNWk+_R&r1(UJs8$`pSLsn9?nQU{|LuqOt$Fr^jDpeD$-Gvh*1ekD zyYh0_yU$t|&s;UDqv2{feE$aUN35`K;3E>|8x-oYd`%d70q4R^&WlnAJ}q~Ih$a7D zc8RpD;y5x{nM>Oo@+aNVLV%Qs-O=l)_s1(;6GJXEjM`7!6KgP}N~DFAhB$j&<)pPG zGuj;^H7L{Q8ZkX45!FDOXhh|WSB^JFTWvgABB^%AC`}3jj}}pM@TDX{uEG$`s3;Nk zqj^2PGC`nJ3S*lTV{6nVlhAC5gNCb}&+e->Pr2&-tEUPYqTR9ClT*Hu9)BL#B85%! z+|7nJ?lZ|0%Gi@vwhMWZE!wFh4_EF%RFARt30Q~SeTjdJpGd>AO1OnEBTDvF)w*K_;9PiWF2eX?_5`y zXe$QrkUPF2F?)Q4_veMTl3h2#uK%gX5ycOe55w^BB*gby6XG`d0V_+sNvbya7d@)Y z`s(qv$@~_xD!RVsFq~pm68^Au>(e1g;IV!+$%XR($EX4Q#7kELq_0JqwBESwYz`v5^gR9b{qGC|=@dEw1EEngY&1oSb*6y1pL|Hr6YrWsSy;f`OS3}&!< z=*(5I)-9H@k+uyvEqZ-)!{Ro7e*KzM5O{?puPnDa6U(;$;K(|ygq3?TU;IYiF~}hj zEUge26)cmPu*bML+${f|;MnPDK_1aKo}xG5d%5S(uoy5~+fS8_lI!vC#0Mo=}moAc`a&hQc%x)XSFj;OS2H>cX6 z&ejP-(r{N2j9#m2*aLdY>AIk|_|kTww?ubk@GHRpsVvli0P7pVt1T3!5U9$gRgh`zmaqd`@o&_wR+9fV^?Zg4iLoWkkUwhX=VwFQEicl-y z?v+vUUBFTLGyVks*(?ztN>tz8mxbnn!wgjw+yUHCFC+ZAWi3R1)`-w;P9D^sZ{ac8paP3$F~ncS3C}XKg`C7yf89HBb7wCYRB@OT&{-g zY{e+ojx|(!kUEk)!jnXd)ojpTF|+=Jb*8O4ElFiH4bj(XSj2u1hc2QPt92Jd;?S=SfR$_6K^~hw1{;HBw1R(&C1>U5n@pR4Ndk z=2qTe&>3oo!$~fU_{_jC2XO;oV;?;${ha^k%h=f&-wY|?+ge{wW0TLcgGf9;1W5Qmes~K}fe|P#@EducAvjt;EZ>=;+-xivYo5$$)0o_= zFQ~guygQyE>Q2F~ZyN9v?qT&^9O!hYS93ui3s=jg`<7K+RwhfoCDrIqz?+rH-jwja zJtDktd`5(hS0aIcin6B;^hb8BQA;Hh%w(2h+#obe>yZ2gEv3M3iqKR;c<@gtIZ2hN zRXZJhIr`FgI#O96ZWRkAXu;Z8osJUS?g?*@8~KX(cm4h!gx4K8kYuxli`0G`!F2-ytQ>d+FhnoIJYX(XrlT^SG>;Bg#u#Wx-^uBlQmfU#%9aNL| z)mQOEp+aY?ue-+4TL3CvrPGzu-D1JGSlAS=*4&;}rZDoA1_xBaC<31cY%tmLi@=Ce zvQ3vV*R>(P8+2&%(2f4e(vDQMCjKM;jwcs)>cy(Zv_7u2W{=aC zYs?_xm@jjMv?$DtBBJb?7 zgGM%K$p!m|pBuRZbQI4OF$xtZoxs#%AIRmukpDF{#*voZG;eFqRp)6WbhuTWCuya5 z6MSjERjs-wn_nw43N$L}45(}TEhyz*9iN=t{_@@Ml^?HVpxy{CnoJW>j?ML(vY^aS zYPYC%Z}aygC--PI_TNtIe5C7;Q6?p_ml~>(cR@7X0J698214=BMc^+i!ljYXf4t4F zR_`?35g5POG}YCRJCl~(5rt3ErydtSJM-$R{$!n2o*w+V$d-_q-mqxv%y!3U#3F$~ zolQ4N`QUqcpVh~IdG&AUn|L>R{ z-If>;P{*RY$OM0EEIvK^MBMD`uUwA|^t*6+yjGo>vFa*&Z*I1%K%vtXh*}xm2y~4i zH!V4uBC1rupB16vhmP-ivFXaiz@dFqWboteC&tF|#?sqbx1F%7w#B-KTgUU-W5jT) zR}5^3s#&Gi$No>#;uBX)XeE5OH-fR>%E~IKHd?|fSe#c)Fw;;-4?VJh?E`>N`bB5= zKY7LrHNofT8C?_IuO6Y%9X3d{Z4xl^sso3$Cva)A zv9Ouko#2)vei^wtt-&z*c$)wbLKlg{@oAc_N@^Kvz?RqYxF8dQ_FGIUV!WW z+pdLEyC(EvU}SJO@C|G(&IGfAYS{_BT&%mIw;XZ**|wpuji5Ezjw&qwiX*lBR{?+o zP(RH&y)LSvJW8jZi8uHoitvvF{eLwkN@l(IS$k@nqp)Js+U#*VNx5Jy1nV!3Rm>R+I7m<4(eL` z*WvHOoVimmWdpTS`He7#8UkIvZBuTaUhnwZ>V?Nc!7S&qk$iQ?>)}`%Twl2rVJ%?_ zz+MB;O(>PD?)|U59_mjA0HqXXO&3qv?Ma^8$dnkBMEW3o-|VK6S^N}@4najqI8PS* zWoncr@qdR0I`kauwaKvQdi;0-99<+0NT4GGFbU%PcY6=`q5YdijdSgh z+cgznT=QCNij|?kr)^wlz{Y(7$iEx{pGOLsi{t`Ap3g=3064O2J^=0?kU>yNp%g8| z4Ix#0Y2=W}8!y{Yj_Y=&ZNK&b@6}^y{_6Y}@Gk`DiycWjkib1Od4~r7W$q~cQhRK+ zD#tDqMrbroKr;fZd>e2qzQTXQ?`mxlWqaQ=HT=XA_Z+z?T%a6Lg~N>V_m1HQ=2n|s z^Ht80m?@RTXj0hiRzpO<9%(ZL%r1q{X$E=`(ZjphTx1NyvLx@yBKnuQvnx57B)oyt z>VNCYQhz&#Upd{vS7?P+Yhr1kp&;2Au7);K`-CZ}o1$uL(cSc03wSWXV@dYFDelkt zkEfVA9oWjNedp#hnCQq?4l8(+I(Y#z7ScQ!5-dhL3d z)vVsIy}x?(P=Z!td&|FY_trk6l;U(+OeoeRbH__Tt3g%+oJBT-W#SKGc<*18&3 zd+%+_Q?ez`cyC*l<2_=_jy&SMVml!_Vb3Il6*grC3T2NLIw+--(iU2vgO*YXgqG3L zeo(&Z@}Ku!$yR`ne7_@0PGU>ed*Anr=RD^*Pf;Iu^A-caVFc3Y@@SB*tKkAQM42Bp zEj_w!yU-p-u`w%ttziQLK7;T^7IL%+oQmmIS?8R8lg)dy=i?+Ag%OXX}F&3w^aN1P#Ve=(m}kEU9-?FoWE<`@)tpZ|)^$#4 z$nLZUw_X%!tH~RIU*wq(lt;} zfLhP8HZVX&y;I=_dK2Jg-w1&xrC6*@9q_x=EJId=@}xVN$EC%^cCxyf{7Z@6>kWo> zSo({}!gRh?Qc862xzjvi3q<9&2iR3|8CxQJ_azcDKTZA|%Y~*MZimOa{`;So zo&lM|pOa5tQJ|g9^sR+f%X&pVcj3ZJq^_?$1QGhH!+UPpJ)jX2$VQ-8^#!3}WywlobxY%1KqgBZgm0 zlApk6g?-e6mG*_wz-aSgX*f^qDV$vz3fH$Z!3w?KwEa8b%e5p5^lw?wG#nzI=%cDD^D=Tm}oCsZ!9tcyTL0$hLq%2Ca zsu-bA@OAVXOcXaa+!(~^Gz}51GyRAnf<%@7l)>e4z!ea1hyc*tXvf~nQ{e1-TxHYe zD}HqUF&K7n+iX_8jmOjC`0YeYCIl*3C3P++^iW92jJk}#cF|P+7;Mj?2Y>S%Nj^6~ z{T4NE;Jhu7TI{-P_z%+1Q=O$!ZnI=$E5-oWRf_aza!QX{(c|uM_COL|&;u2HM`n+k zoL7-f>JAPEp`#{^nZZL3$1&Y>uzIIQbrV%xMg|WYgcS{z-FSnr28hzc%JL0ec!}cI zPv1rTS$Sfu+K=9z6~9C987Vp_B5}@7pZo7+ydOa)2Zni9woyM28aN>6)Hyv}k9GX_ zLmn1hd2_PDB3pFkZ&J>4R2rq&WHPw%QLVwC9mNdMXe1J$4oQ%j5X)}P#T%kgNRQFH zlOr@XkK)e|`s(Nse$Rb&cn(_j9w(`m$<&C&7J*VtrNqOX%Yg`#Nr~Oh3YjV zwy}pBV18TSaBu`m=?FEcYx4Ch>K8_d1SfoKSx%WL-PTySU~bQYqs3$;;K{|Q4Gx78 z!&3A4ILyq!ieZ#TadE~q|KW;05x*?Lxzr1BA1}q4uEKtCjH*f>Ao$9y5;krUf+7A_ z-W3xSjwM%UxNUJ$Ye0>h`*3^HL69JJI7&Ze7!n_kz8a0b`n(t`einG{&%VC&$i%8n z48=F3GkhcKA+X&UfKe8w^ug>V#Ij`w^x(4TMS}MaOdcpeGEG=aNI-}2HdKV9Cs_i% z0&SV(Sw}wqT&%yx6wWI!Ho*uKoWvDMQ(F0Aq_%ow8bX6q5y=Lbr}^5}C|;P#oBg5vUKnauoq zGpsOv;Xa{h)F`|cRJ0ct(zRNCgoo+F_2kIy*tdSnc1Lyk#qPz@PL0x5Gci*+Lq81^$hw}C-=(!lErWN;bpb>J45)ATkr%VYsg>wc~JI0utcC#g-B-Zg1 z5HhFkVjMEo15)PHVbPyNaKkX})V^`OT<)ieMC3^B!EEZuVx_z^Sa-;_C8*3>511Bs zyjC~DwXe-dt&jpf16`=J%lwpifbQS>ApQ(K{_RYgZG7RLTd9I{>Hcgt)cA_qNqbuJ z%Kp(wa}5-=y?^iC^VHrwYPzV6{M|8Hml)~fAdkn?_h`C=!gUu8?2gp+v>{La>#cik+BT&TLt!fw z-q+E%c0QV$2|6jxBvnzS4;SU|G>wT4^?;~F2e*iv^o>_G>HXnFDda08+#ZQCzbF-- zkUshX@$0phU#5C9oZcc^c)O*i#F3uP*NF?0-sv4dHuFw_{Qnw^>qhP*KK>6wql0gs zxbemb>T0)Ry2!k}X|<%sVs{tr?Ta+^Z|;D%`+8*eEwf|v+lBq?fL#3vI^I_TGo7iu zv;2At(6vNL1InjRnqidHX`BXTJR^oRo#`d=b;c*q5gq{Hh(L`z`qqM_f{|dZc8AE+ z{;>7N!Xk{box`E-nEI>1$X`C0pPQReSv_a&&MQ`!d1}b#Lc-K(f?MN|%Z_BSd{Q}A zN&xjH?OVz_z%B-xogwGy))AM9WZQjXJ~d)?7heP!H`AE9MG86rEeUa{v8z-%SnqY;t?YX>%Ajl1$=aoS698#8VY{H$GKdao>Ft zPfa*&`hCXYuS-_i)>9OCKgiEL1)NLte(KRq{n{`kfut~-b3^$$9KA;h z1ruJq^M~;Dn_r|LZ;tc4^L@__78=##UT>jd8&{ze2=xY?P8?CHTx=M#xyicCZ1nhd zp0hPDp|2e*7?cf)QVR#E$0QaMnTV~>l#A@H4Y&jd+egapNSEOph|s`8(fKGiAAr)} zFY%g`6cK#U|G=h9{&!|r5om?b=p%XrH;#x0MhpZS>Blqf?06lHbtPWoaVd3j0hjB; zgoI(*21Q3V(6X#qCbiP6!gFzn$TsB?OIS`p4puW=gSoMq*%~bC2w@^VAdJLME%r>S zwR~3F?sGdFJPDKz>UsDGC$$2+;a+QoSi+Wrx27Ho0c&Op4+@+FmVQ%DINZ_Keiiki zSF6I&KPxfa(Ser!p-|6}5zK@XfvL9=k0T?W6U67}^UpTNn8UfWd8&LQ-Qfv#UEDJg zj1SPjeL zT)U^IV2fm!nC&4ZGS@QOGgmVwwl2RnO1)B|^XB9>Kw&K4a3~0Zt&)K`AW~-tPA{1E z8?)JEQfaDG0`!#Yu(7G|maaYRH;g|rc0GKIJu*K2$RqdXsOG({<Nw}6QMd0;-wsc*-xncmtb#zHjxSf9EPs}DGaJB_#sPEE1f84gufyl z2do0(w149sfp1&)-)c-ISIDrD>blwn_Pj4z+<%;E>&YGT&bu?0vm?5B#o?Fo>>g8f z%??vnus+qz2M#OI%;f`6UF%RNFy3Y9tbkC<0nU^3O(|sWsnRXdrDhRHiknzU>c{U> zf6po1brMtj6QqyNduV>&{QkZxOLo?p|B>ySF;z}AE#&uAv0QJQ zjn7x}#19BCo>a6dPsQ=yew(@tE2VyfE&um+NNcE<320Edhj_}eV)=t*b%6S%mJ1v) ze&3ep1Dydah>v9WY!jqRq7vzJ8lGfGrAy-u;rfAKUvPlFgC>mFWd<{3Hj?B5LDBs=4-pQt!nVM~_6eX~feS&4nGS6{ALzvv{LA_@m+Ft59{2uF<1d>?rNmP`;yI z$0%Xe7V_Dr#3bO=k(*oBFF%tXj~BFA_x$mXvH!gC3fA1R-+Wzk?6~=0Xi7Ju4Yg`# z3_G8t*u?WYs8Mga*I5|aZs{&|xlqhfN_5iE89Kf<<*>_9bSA?N^w0HR-E*M&AMh3=IjRrA2NQIbiUg z$}cMv_}BAdma}Ba{Bm2z&&^zp$5Xe%)E#iSTs&m&ns|H&;Td~Z=yp4PX1>Nik}zJQ zV9(>c@Bf_lo28FFx&(3`$b~Ms)uL44tW=`L>;sdnfcRjA6m-F`tfgilo{9^pcM=A( zJIy)Qvar-NhvRnpRP#ch*KS9KFj6|x(FKO^ua{aa!;Sl;GQ)I`mD?8>EGrufz)N() zD*+A0lI^JEvE(frDNjORwOEEwEBtOQ+!e0fjO-^=y3h;)gc%{tNIUS58Fp`yQZYRZ zlqnD(6g1xX!N@nPtL*{miE0V%T7tH^+cR1{!i! z5?@?Gk{_LA!2mmvLL$hIq?RYb_$}dy{qRih!xLU~QT&VjWb8ij+_R_~AfC_>IzICN zOQ@gjib2$7F39`PhAeynm6d& z1mng2Vam?Z<1;XDc%V1kVEi`!v7`K-j9Z&_EQ+Z=i5FYWU%ad(wy)ZlX>G2VGk4`C z<1dLy%GF+`k|M?B!!Y0lpHz&>uhXV-I4s>}$%xCKgW-Q{o!zdCg|LGwX%0Wq7s~-g4)n)BuPGxgiqIBB zgN4Kr?FZO9c9dpqYzxMAopmh{dnbfpA#5JqL)R$vzF7QReIv$GD$=vdOpfZzOr@IB zP7F;@?b#lmE5Bma+!`=uIR!dNG1121O|Xd$vrL9@uFYW=$TV!Jpc$0%?nP>Mv5rIcveBAeWAF+l#w2sU5W&^wZV)) z%O~yUd}r4-wG_Y<4&Y7l17`!3j?FB`Qzfo^f@2Vvz zcNuH#ag@Dl`s47xo2HAJE7+VB&R(oMUm;Uw?kS*t7??5+7FHEXYp;#V1 zl^b=rdg{Ae{1%Rwv>j2&J^VTC2wNyMnab6jxjA|ctE4keKsX$&=j+;wvyDYM0qKTt zl|7CLW}I@lP~kS{xrJW8hB{0VLg%h@5rH6_{b3BV5sKulTT8UMY_x)TWHDNTVTps z4}fO5f_?~qZp~qrF#*u)(dc+|&z@-P&(Y|fXira670>`uv(mU)+M{JtQc`a{*Tw*z~Zy{cN~?r#UB#!L%xqxYx`%N%Wl2AU#C%EdI{Cov~Z< zcIII}&+NS5JjL7Jx|@1k`R!dTxi%hGBNW_1e5#P_Bb2xRSQJl+Npv;_7KRLSk83_T zXL;v8v`Y3cMP0A^bN-Y=b4Mw3TYPRZJQRp8*6$euW zc1x9MCVO%J-i7QLbJfW4<0Hr>pq_z8>AlZzNAge|0%O_bR^Vncs@lMsfkqz|=1(_Cq{ff0K-{bKJlD|0JUe|!~{MuF%i24htC_7>C@)4-A#*CT^5U@Xy}s2 zn6J4RZ}`)|$^&Z)Dn8nTC`LuPUMA=hZAh6w6N!$9q8fS8JlPybu9*4sjh#VMcY8wrL4f(ic=iL)? zMu=`vZkGmU8(~3-8Z+#K4HY709cgn&2Mz0_Pt=KOM~>nflRQ}W^?g&d+}Hw}thMEC zPY%GL`L3Dl?ZmeTj$u)_^SO(=#yI>>FAYp*gBf0bqB0{g?2k|u~nt~P7!O4 z&uw(kbh9jW36un6&X1|@V5{NCmd$mqOz*U3WH*>B=_}2HQBNRhpHOjNBEWFfc8A2R z0`zaXbH1okt4eFydIXw@x>1+S-`aORd397psN?n^aZZpuK+H(B-A&K<`g*_lb@l*E z0X>TomFtit*jlq#6MJU4Wf{-T>idc%kVx(!zEFtwiKKWc^(pmT5Iv?X!Dzq_U{RXwyekD#ie0VJF{(k0?m;gAS$*ba&AE+29YfLhiDP ztQ-V{jOu#+dJs*+@3a;ViWrmSgXG)|ojg>Sf>TAe7bFzL&idjzdK+QN;a;ZrLLFuv z_bx2-l8udI4s-*+FppF&M=-o&i=nTp0H!|UBI;ijiG(d|A@Ii7vZu)1sjpZvcl(_# zD_5chWJkoE;0s%!wuSL8PiF@t3bvfYVmaihmCU&eqN8`1S3wVD)O|Tt^1K|cq1jC`nxZi%lf7#(^dEnuMu_h<)2nmoT1X(}_ypvmid)?5WgR~?_l zBJ2JZ`zi6(_xR7PHaBA<%~;FokAAcavsP=#mz9C^LTeQ?CFVI@8`aQ6G@jr!NwGh4 z!nDx4>&ULy8zUoIM(SI!f>taObCf{9@=>*HNy9W|jWSuC}Uv;u7*-hvp9!2jN| z{79nB5aLW!=YNWYW=bNZe#!`b^00QM- zf4UMVdy=KWnh_Mql2(IkW96 z0V(GANm4-iF7+OWuhIc&AKum4zO}4uV#l7XFLZ4g-i&qU4(9$TcPRJJ&H_Kam3?j| zCZPUJ-M^*y%Yi87j-Ws^qR}vcC<4~mbR7@nupVHxHNzz2IujvZBQYv zL!)-!lP;wv2b75%H+dTQMri)kQc;qz#s4kW8sy{EEgg;Q8De#UH$Te1Xzx^M_f=Ot zLUr&3p2TqSJ@FHHFBBt4>J#w`dYvm>Rk3Vs&m}6>=}*%brxm^tOFinwvl0D?7;BWW zIc&BpUtAIv!!V+DXYUa{lDD^pIo%}#D73yT$$~}k*#iiF2)n$+^^FV)c0CJkaIR1iYKkIbocz+2E z4A86~6y67w@I7&Jccyx2(&DkqrF~|qGmCfNUkVKb&*rJQb0B$xFRoCjzISfyLy~+` z{9cjCXDO}%PP8x0dip$1I#rmqx`c^WL#DL3wrO7GCR_;%(9!~Sg?bUb%Cl8YW4;i1 zQ!z;vQ~OG6W3IB1w#CxT>6Y}s%=SqB6rJh)t9|zaDAPl&U|`>^z!yTCXf;Hy8IbFh z04w91ltXB@#q|wf^C*EN4XF*R5+W6V!va_d$To5`#17J7KbUevJXFddu%k^otO3er zXn24r4r+0ll$_8AJ5iE>Wr7}P=z!xZk+LN_T}(`h71u~{H2B{_wa#9s^WLJT?{ zNJoA#S8p>avDAK%c_vM!bvOAe3Ag`H|FecN#y&{utmf>>GJRn#Na9id14HEhPQG!LxE9{TJoO&t zp^YRR098Ep7>2s3;OVKI*(L+d5=BKUB$jt57`)m;lAb%x3g>duF+Ix7{m{|a532`h z6yp>@q-6bkLsSo|nrs`P-xW!|Km;26Qq3&Bf05fkn-?urVp|>nxjbx3X?DmwQ&}W4 z^T*#J^wdZTHTzs{-fZNFDfE+_5 zA_}vPWadg`Y`F;gB0}YMdzbR}%pJcg>N1U%5AnQhuioCWb$oH5|7REpO5aiPw~^w! z%&}By)=W?_-ZWp-rOomLw=YDRTm7h!_;0P}+`l)fM|C8`AG(J1AnYro`jUv+)&~i>=Cu)w6q{7=6`!>;KSl0CFmiIT1WW#bXu+l^?%WN*QwR*|A zIZvOH>`^+v`eCV8XJC0Wp%(uMJi|eFxh*P%tFya@Er<$CAZSiK`G={-?UfTMrQ>3| zLeU@X>guBYMwXJ)+gnH&;d`OPGH0|*^`5h-L*+^KB7bk)c_;EBJjfjp&xncd<6ap9 z?i%c~uOdG{uVKd5#!uxswu&V3eY9k@-<1tN4vqHAm_h&9%b!>VErV==!&j;U@+Pl` zJ`Cq?<6`*nV=zxlIHZl;QE2pJ z`)k+e5~qu&6)`2w1mu5d0qU^Z_A@W2=7hI)xD%Q)M z^x87@U&QL3M-wx#K6XM zMqMOb%k0+%927w{*ko^2^+5MCW@WXiezy&ZBzCj`i-;BuK7(;KTjUCiU7Brk9Sww- zN!vehyk!-M#1?W7DS6bPQbHE8!f%2J!jgw;NDL#33F`bTESFTXTCtT@g6Jyj<>@6_H{1fY}fwy&($du`tmdf zJcCuHy}GOj6Za$aD7edCJLlH@V@lL_is~Xvex8JLV@UQ2oDAe_r>LjWJDM9`UQ}wL zu^lMRogUhuKxZg%ya;t&}tG7;p>NCi%d#$`7C! zHV|2(=#P#D6NeckXHW=$Ile|LaJI=SUP6-3kYtIsI>XuMUBs#mk5yq<6;_Duhc82H zzKU{vUcQ0s#>`iQ$Po3O&F}3asu6eGYI#nXXZ%IK- z0X1ao^U0i0am_px!=9?aO0k-o5cLG^N^7ada*amNIQ?V6KB%!XNT57R)?ttSprk z8+EbI^E6&hM&+WZJz%pZ`_^*Ww;s;OM!r?ZQXGpJ@t?)=F?q+^zo`ul_s>MOZHp8i z$DS8=fu4G-AjD>gM?phIb0naf$;#f=GGEwhvnRXOli}U>BG(E>3Dy8ONG5fz5azx zjMCM{w!TAA>IdSgd;_!zc%?q0jk>-sXi!Dd z(gTW7u0kpj>hxN4q(P=mHWR=qqdh#_4WOQ)zhMP&si)JNO}(P3xshX ztqBy{x&h+|Cc1V|LJ^nTgGhtXZ>9&E$E^|DdaBeM#Bk`8qI36u$-n~fj{om7kJvDn zrSqXg+d=&b$UBfbeneu#hvY9wxP1^XrWbioM^_yA0Cp$c(bYtEbX}XtDWJPkSt%fM zWBn`hjOc-C4Sx}aTMTBaT<#-DA2x0y$vsvQP$GYceX$~AqO%d=qoNXb2uVW9A0j z`ZKgWDLLR-QDX!M?H*y~UlubDYN1dl61`(K7v(4vPFYD=#v7nk8T+IUMyl?BQMar- zyI8VKsTL}@20~CE=H%s)cRNg(ct!CxV_UYtXywU88N?E>1BLDvwqp;< zKd?4P*km#zbw6YYeL{=}X~t(l?0d2E17*} zzvNOpwF}3DB1d1lR{aJ^8PaTHtnf&9s;t{)&n%oc2iSFEew5|BcTtZ?E#|I0jLvdG zGY8o=5uD^9n1!^Mk|Kv%Cz`;`VvwiPpIAw1r3f_YX#o#Zt1y0@$}O-QXeX>9n}?~15NNB07+vR zzhBXm<s8g2D<^BqfMJji^~5uom`4 z&cArN5UZ#F3PNpWnS|6Bpg&s7*`hI-R3hOZ#y8{=gq8XeUyfibwZ?(HS;hgCYPtuD zsT~;sc|r3#S5x+#f@Z-^0{{W}T=2E17Rux!U=z?TM-qWx5m5ym@j>YohD!qWlExH_ zC{0HB$62Z^Y(ywr5M+@?@Uo`nQXH$Fs>h^|V5sGMnM|M-6dc@)EtAw+=@3el&Les3a`;+@mC>?%jrrZ;w^p&jEkjfdESF(OQ6Caedz%? zYG;>imdzE4;iD3JwFbv(Kea*T!tTN)4-o{GjkS(flVtUHD^?%-bJIk7>Bn-JUjM@C z{;hNMTWr}%#b{ZcO+RPInCkxYfoY12w$TuA@(TDmtR z2sO*9lo3EvQ5PA5p({ZG8qK(+;VMWCROfe}N@m^eRqMDDERL-Jc~-R;`M2KM8+IMp+Ch>X)K+zl z*JrApH+AOO9mydLy-KVrvjZrkkzwciHWv?c4)+9$TN^eH`E$xOza8)ldIl_mg@uEb z&AveoxvC{S)KAoTGX<9M=K11IJ5Y=_u!8qD?>~V88YtdzX}tL-uwMjmA6*N*S+gjf z*~kxI52+ID1~`4NKisG^irQ*vvbq`82MNR`xoNo@#uNM>8Y%0+JKq7`xm0c`$&{40 zxOF<&BRKy)>W?xVPpNcbqT4DO2<+0`)H~#VNx0p}FKs1%?RC3yGHIwe-TIYdn0jUW z5RzC$O8CPt+@7i9ilk!_C6SianrYVzWJOq>^hcm3=-eveR;EU%7HHu(7e2k%6Q!wS zIyLBa&Sj-dBY@eVPd;|km+yZqzD0T>5TWc!BQm(Wpc1%)^9*QVb`eC z%#aFH>jBj*V05Fr10|@;Mue^wSWq<`4JD@PKBU@$D<;sgM zKJ*YZdX|W0&Q;h|ZX|Z-rhc0ZY7V$?3xVOL3Ag(kBt-mKU!SGN(iaTE19jE7t9v20VmLx=uuo8)?gG0KZwLXj#_ojU zk@!0b4B&(?G1k|!(oO+1z-oaId-5W!D<&}ig|kyaAAq3V4I+G$>S=kWWmJ4MIgWon zqvO04;+IMX{6SCkYS&<;3Fqr=o2Dgolm zcf%y0UsX=8G?m;apL6S@_(SdOZ#^%;$mic~YkT_ziR1<9WM=`?**qCJ)(KE9<%%H9 z;>)&3baM3;tlp*3%eFZiYMfDdhNGmt!${qx5g}f^QRTu~m)qMsQB{t$w7aJq*>Yru zn0^s7!6>4AaqL&EugG2WHj*4CBx-{1Z-;x{X; ztt`uS%Zz1vL3ce#)^G17>+8wa?Y{KPAXK{AeNLxKppoPe%>-|XWC3YKfpM<#a`Nf? z3V#4WM@`Yghoenl%om=YpQqNk$UAGr9glX^k&kwWYpMHOrHlE+pqjBM*DJDpsxCGvfj^3dp4@fOkTHLUrP@FPC`!N%} zBjz=8+uTxioY$m>b39Ah)?Fd7HCxGoo z=b|VMMHwWB7Fd}y9lpeZcGi`Ka(|y|IuzQjZ7;|)<_cksjjlqq3*hPh+~IE6vr9oe zlYcYyzVfWPePGk&sn*k7eGhwiTK^>BSBmp3QZ`spdnlaCSex#|ED&( z|7j$&|JiJ@SUlu<%3MqgW%rm)IGeSpcyf0KVs z&qyyx11?RMPep44B@m^WM8@M|EZ$UU_JGf&A3l<)%@Ex=Fi@?`&W_!G6gx_fF^Ay7 z0Kr8B7R?m(cy_5tQr(l8zgU$C#G3Mu<`mhWzc((^l4pqP$WE~X=8y6~FVsa7y%6Y> zGTF~pf(JWP0^$)Coeu)=&BXHnJu>c<+0I|ici<`4W38<)ZLjO2I||Ox9c`U&y4rLg zaD;fft9BDnA9*iQf7g5B7sc;=l&;9O+CyWOaDmNc7s-S^q7IZ%*}`@}{%`TI3sk~X zDHor91BU+PjBKu19N6~SAGQt6)NdZiseL2T0Uwb|J!0{`{CsM6mTJ^tU#nX!8i40& zoE#qsg^ET{NA=eIH{MpQAUVJ^=H_fwR&wTCOz)%1&?XWJJRDw{ zToK0hgcb5M=tL#SgGEJ0=rqPj2FXsKcSYL2w|l;4W^(o!%)U*B z+|;4TNzSDI`WpmM3I(foL0snk;u5+)lMB6FNRI_o=^#T4s$u!uhy#o)8{8o;N!QC0 zJ0@YtXhH}IHsS#Xy=D9=y6O^eC%*f1rQ~>R03Ui`23vUkH8J*~k9spT6`B+u;c#BJ zYjvqCp0lKC)O4(?`vR9U;JY+`ntu^1btjRlnO0P(XQh7a%!@w9?|AH5TfWWV$nO6@ z>L@daYv#_7YvZ4#7zd4mKke42x2RQ8nMolZD%O(Q47nM~5wCCl{KvPDmhR5Zo}P}_ ztL|zAk=|(wR27PjOI@8N$+o7iq~0gn*Hdr6$n8+4M|qJP*rIiiIigy^Fq+2>M*b@d z5ZZ?(Dsx|3bBA?=@yHA-giH5-fgNOaGy|T?A$CEL4Gh6q2PvjgK)Hx|3Mm*Vtym3! z+Ac@zxIm=Js(}C`->-J@<+3cIiqDy63oB9aiB)Yf8Z$ewr7VSfT4Lr?G4VSj`HuL; z>;bcc`aIL8BvWC?<+FB`QmL|k%#=lAa}%RhZO&*D2m$+5?qKBHNNdV)9o&wvxIZ9wH`t#$t9||FfD@9g2X~o54-U!5r}}2_}&zxHv9xhvHBEj_)xS- zMk{4fsj>{qsIoccz1WVfoSZJ0*R~An`oC!UyIO7gU%OgW7F#t14aSM$F}Ahtd-+kj z%{u3HPjvy~Aw%g1^c_t|h!l-pTu zw6g0$Y_M?w(uFnk2NQK1nvq0Z!NfIqEVcyM+GQGgM*`Wl$~$@GqtfIV#k zd%8VkH=O(UbYsZf6IpGt+dajc#sfn5MhJH31q8km*kJnK)$kSVbDZ!6f;hiaOQP#W z@U7B(yAcbBuenAHO<-NRJ_YV$zQ z85I=;s;UBJi(50G)(SYOmh`b_4^B*Oc^V5cPA#@69SLl4`ni}Mb6v2h@$EH|TtnCb z;tIAfA@E{H>KpbpWM{+OkhnmE zr9>iG3Z;7{B*ymaAwx5*7`3-uR3MhSGv`X&1KqRb;T&BuW&NL(i}xbRIv-*v7vM4| z1}~-kR1&7G2{; zb#COM82I6g$#py|U|lg1uGiOA8>v6fx`|w@=D=@>#RmT7VPYzJFgiUwJiPa#16H8P z-rCc)TT7GkYEfjxI+~S_&~|Z2(H0&@&?Dlp z^l3NW+;^e|vvzpGmW~4W=&*!69UyXLJJHwYa4ds<+dn;?HH(FU5M$X%s)h~L8K+}w z^L$ZFfD3=BDFD)QR}fu z2o5N^EfrU53k&sPy$b;M&3=#+NJv{k-75qOjHPapI#dIpvPv8uzgT<0D4QML1_E9e z@W3cIj;)*v=maG0uacKEbq9U7qcTruSGgy@~SJqZN$iW-V z-z(yg!+Pmxn$}K}yv-JSlHTRX>q%RcCfije67{A!d-tKSAMR9XtJxlM&? z^3?|+8>=LI$D$p#n&gdVU-G8NyV=b;) zhOtm?w?||kONh-}?kr2#3ds@1sqwqL(XQIO9oa2oY1E3~7nfEPZ_kxiR|~r5yM(d( za#HhUx>Si2$GQUIo*L?(HN_fD+CYA-8;Z zLsohhUs2Fe;?AibEUv*g<%DX8$5Lf?m5Z=h>WJtkyYF3E(nwJAN-FTi(YjfnOCzDe znph+{$Vc!`A|*#B9bgYAZk&L5?hiF2N9Sh4Q*mmjj6XI>^Q z!Hit_R;OeLOMPnwJOAOwsNG_TJa>CYJgnK>WDe^fdpfX28FF(nQ@51O8oM*|K8a%vf^!&y^d8V9-(o`&UH#&K+Bq zhOT^RWO3Q~_A`&z6pF6c4L;u(dlbms)!hNPCs=cMpsO)33G{-$H_V^d)yq(Yz|SM@ z)c0YJ6~bE8K+jrMPn6xik|9W{_aD(OO|=N4)GQZ(}pGeq<;6EAxO%M2TMTU zHe<91eNfLEtzK0QO}KJcQFXG2+f3WbQwUcQtfb1VRBZ9h=XYO+WWuiOES)Wl^c4;i z_5}wck&%(XAh@BbfkN`Ubh7YRA-Ww62B||v$BZtIW?wli$+q%j>p8Tolt2vXhMwD} zVDAZ_ic}AZ-(BFt_t2RrGtm@fqu^JP@m#co%rdUS7#mR(jmRbEt^c2S=}cLB?WUfx z`ImXvXN6nE)CqC4NIRvfl}(a0+UdPDgStaQL$~Yo7Td)Ii~OUhtVtFNQ&F#SIM0Ln zCyw#m*XB5%2R&q0y2Iuy8ak=fWVqx|3GT@Mrb;mWpA-&HHK5^f2A|>AR6s+FtD;)a z+!PMC#;(u{w<>*pMcANSUOX|Nf~;dese~v@s%%enRS5*yUDnLPV$q#C?Nqt~io``- zrz;X8orLS-+-ELu>}g#M)aT4hQAQ97O{53UZr!j2r}0i8`IQH00EC=zTE|Ab1!0tO z!w*?1Rx7FEos~PVSHg#eNNi>L!q_uBmM3VdUN(gTE)XlxLZ;Lp;7#*|pw|iFOq*Z= zUUTc=nJSX{HMtnNeC5*6XV*~Q%VrfR<Q($SoPBbtez`Xf^HRFLvP>G_?iW($hnY-FV~m*j|}I z)~8mxnjsNWYn;tajXHN=Aoja+Jtm`QNW|xhCVP6Y{_^tca&xaMhi|6dkvZmcPN1`t zce&KbKK=dh(tXHD1DS=NVgmb#G^`nZmYmj(jq~Hs{-f77shWe9alHfq$qiUixJzXC zG%iBpbu@;~q)+fH5LnsKYM*7<3t}&Z9>?+*tQJxFmC|L*GtoQ_EAOc$o+s!;aW+__IiZ2LXaYDc1;)_HE>UZ17zCQ9tMS5>e25jq|JWX1% zX)U5d(aTUEO7Oj#L0Ap@&`Zu5$&A?B0~0)sXjs5v8Cpiv-X3cPywD@e>!62pyGDAT zqJO7FCdF>@8pz!lq>)N5GR*`tr&{Mr`_yU4M)ohSpm3Dn@4gd!JmR=A8k7P+ru5L6 zl;F<50Z$0deVOmEURO*|I#Jhee6_(aoSnc`lAo%bkG;a5R7@!kDW(F{N%7|-`MLOK z^KS^=+l!=!puRN-+liO={9a1huFpz);N?3 z_TfyR&#+`9UfyKwVXmOt0Ti0Piz4~c`O|8Z^Zk~c_wN}t$WTngPO*S}{2};x61e5Vpb!E*n{o*L zRCMMI#~&sWur`88bb?HWuHP_rEBpjODT>n4#1XQ%Sg`4X!eF_=4 z{n$j=rq0c+1K6&v5;eKip(Wi9mT5vHb2_iN-+@>$u$j+3z;5+_%5uw3JdLn{PC@W zgVcb}nCW$w%oy8znWp3zGz2l|1+IJ)gA&iB2rGqf!^VcFE$4Dr+$h1p1%v{-3|=5L zRTz5{&m95-y)zmujD1$TuNZFL4v(kfvI5gsPFAFSs;tvzN{&VU(Y*cJC>BLhs}PH3 zf-OaT#f;BFlQZI{+6mjx9{&V{6g(DFt5u>T9=sDu*a2d)ayhgGgjSX=>&IQF+p ztHukwY;?50&moR?o4hfRWIP{)K#S@MR{T=O9v>yTf ziw8zX-299|ecb2cn@I8@l02`lf+bg^h0X)>Z!lz~dDK>$mh4>Vpy>wZwxucLtm+c= zvc|vQR|B#{y+8_~Wn3Ej_gO+q*pn$Fsz8$O64f(8Nhcty9`HKzX)wqY=R?Ad88gW* zz98@VDf|W=0AD*L!kJqB!u;;t^Z$jZDwb60$}_`FaQ3Yb=l>k`YA;yR1vvNnQ!awl zMmkH0MBMIXQl`Y;zZz}A*@bUU^8ZXbBXjEG=@gh*l%KBG)M?ZNMG+{=QU?GHpkud0 z47QO)9p}}icbR)P6-7*H?Pl*lRPklSVXw_*^fxq>cpsFIq+|f@-nOI@QnpNp zmr$SGSQnbF+tL}DlWVtA|MQzj{jA1roNk>d?{NaSG`Td;*h*8}KRnJ3REHMj3hN#4 zA_arp-Gc@2!ua?vDk>wT#nb-PFH*K{C&}7w_;1e**&mW)v>VYAXp`Qtyxe#)q+hjk9)Ac#=Ks3Awo=5_(88XGc!_JT2>aJJ~D~Li(0W*OWj!Jn93cS z_Y9f(w0#{QXYFw(;&N>h#Q!gk-D6D%!X2yoFv=Av1j=e%0*@7^ZZ{ezxZOdaxr#Sy4#zUUA?g0a`p8H z!2hI(M4E`t7xC2-76NWBfQ_JDXPzGOz~eA^l7!nwkw|avN~F3vveHYnrFmzveUo49 zzIs2D$p3ke|6jp+9@Y}|6!`&DX@Yv`&Xj}55+elvl&u7c|M3O0!W?UqV12{?Gs@#k z#PDSuva?-R)|DwFsij7t5D1Uhad@Q02~0^4)ZYl~4vEA-4KhtD*sahmL-i1t4gT+s z1lD+N>^O0rXj!yNw0yyDV*ez5@fYHQ<$wK)8kBgvs`Tnf(`KJf6_057obgoQIwM>O zu>*-gr5?TJ2=h=%5E^*mBIuTrNc9uUYgVHxLHw=Zm@@&pHtKs$N<7+kN9x(_~dU7KExxFlStQ#1T#RClce75+}z144|;I6u^^0V_yH z3UTuQJ2SqTiMaPP?94=X1}IECh)rb7ATa^?Krt*os3KP^(9T z!t;bM_9K;esv5(ppQ^%b5K>7=tmaMBONm`B5SWF+Yx~mr>+1T``ZjIaL>a!eb%w7X z#e?|ffq~4}ez7>-7K59G8liBfSPf6zfu29jYniop9C;JyFPp0|6sN%ogS|kU05oA! z54Ead8dpHmw=beA0(aCU1Go~im!g-2yL=cb2Ome_C4YuqS*rV)PAaJyK;w(LJ>!t$b-;_(jxI)W<~Fy0R=~-Q1NZD5r6mzN(lR`ME03f+X! z$ieY_7K_a7_I4Udjb^JcRmzw1Y|v)R;d=`(_BOd5Ku#|A8K)jSr(RMR!a4lZJa3NL zu7b^g@i<})OK)&b{sFala*{Xuba=pV4rkPkgYufzKZ^ELpXCwXA`{O?Y#sD zQXx#r5BuP!5Er3+fRuG-fP->*%9SbCpL#YEXAR{Bax^s-ju$wgI3fTh%%@~200*V% z5=g~9DY*S=FbgCEB=rs+O^J>^_9A^z|W5bbFm^WcENO^jmQO4$J2b+!dGlj@_AV9mA*2&S|*iC8XJiK zf!na$PW0+<*G$06iwLUgmjuziCA+M$tisn1znTvk6$OJ5oR!yvKZ+eC)ZKDtg)6K} z1Lx`Ys+vt=8===Z!}yM{ln7(Fh2ivAV_s>tS;!Vu^%n$#{<<5`v;3fvU7sr6m9CY} zq6h@8TPoxfH`6>_B`81Am21YYgTGn_U=SdH@B+A0@lyi-00(Owr$Jmts2RBF%Xs<- zmrgwbZZvj>R>MQC5TT3!o}r)L;9kv`gR(+XNaInswHZWXsPna-Q8~g)V~}VIRaIq~ zi)A5W7Res)j<#Gb6?o^pVt1~xcu{+biV+N`*aSdAP?0T61w8D#E8~Bvh;9Ns^mMA3 zFTXpoBNF?SEsa(T<09S;CA`5t2PoEiV zg6RZ-A!48g3D?C9rvULY;E2Ql9{7vWmFR+We;s-bmn&gLS+}Q1WcCwwnS@n&q9T>L zX`YR(%(9o}*q6drbK}BR;W)a}j2ssC%H*#5p5tQwdEuU>9+LX)1Yg2YHkj+hU`8Sn z!Jy1)rQU>b=|&S>epKgdKtF756A+cB~zZ<9D?%X2MzQ0E^gHWrvrm|nzyqa!#{*;lL9J8)?}VEiYs8o5ZptOLSh^| zgl(hFx7+>uM~$nU0cw@Nj^W zJ3^bz={l72Z0s36{=;HEKjVLl$3AnseqPjXvB0}rMDKE`bf9bB4Q-2smVFbcS(%Re zl#W9M%KZRe0>2>Vi`!_tO$iYk;Ff9tDU+ukAYvkh79y$cgDT@-GvdD&NUwqW4=VAS zQ*3#8=2^4JJ3Q<)ns-tUn~lCsx6aWd=JSrrzs=!@2Ple^#nU2aevQd`~HDfpeUU>?(Bpt>Tb z=Y-R+DhepvK;?s&6QUOQl~7DV`yy~=Otl5QF<41x(58QN+MEVZjo<1+`f(`QVd5R| zb@;OulL-guC()I{4C)z{3+wfeVt+G<^I`DFA-&$y(WYxCA$fdiiH9A7mVQaj7b&S# zd)>X+UiRe#eympI8M8`BHkS(n2(#)-uzOf6+-%m8gPGj6CWEe^UW^L_#))fOIvtBE z9VEFpmnUAC$WV#IWOnvo(Tu06G{lV*=~XV7#8Fb>)96^KFfx^X|HtWTW`#VRJ{1TH zO1#T_bSl=b4q6HpQ{zZ3Hv?>!?+0vUl)GgOu8;>PH~_;26hju=Em;}R|OeCW}DLyC;clUo;KhM@o9o^k&sKmfAQ|S zW@G;5R((U6TqOE8@r9?7dX2!o1DfEV7O+DX!`_a4STj?yGbh_~r2BQy!c;2u$uJ^% zHEb#to174{VN`~6A!>A*{SlemXq0tIvCGE}OmWJoz1XGcww3J{jAfMmKgPZTAkOms zpXYsVzBk^R+;PV(j=eV_Bq4-AAPH~~5(r`E0)z|*A><%rZ{tWbb;KCAQDYo&M6Gq! zRrjo>j#~AvZT)GhR%`WM|DWf*I|8w3djfZoyO;NQp6~d4H|`Uo-yGphk&9){-EO_!M+pR@miF{n&c~rDfMa<$_?)i7OW$>tii0{ zsM}qTnd(SR^^jHOZPwD$+&8rhH=p-MCB`~%$K6^92m>AGg3HLOLN@(=Sol+3Y^-vH zf^mLn=!ssCksStdn#2;~dJ7 zvrDIKo_=)9J@)a|!to18;)`d5&xPMB$e+m?VKc~PpvvA`5+a)e46n7V7BUpfHThC$ z64Se9&z{}8cW-z5-F|`et_#ykv|Q?H?X~Lke5zlXk!%HRgeMeaUVtnR?tjleoa%HithP@lV-yO$-XCvH=#(U9V;7_Q6y;N}AV?S@^_r-_8)D#WM!7@{c_N*kC zpjpsusLi1V;AQoF3Bu3)fklTHSFT*5P@2|~7-5?wI;VH8p?Zc>*vye@l^VaWhbx|I ze`r|vltXj>7dTS?>EruOlK`VBP3>LSx}v)#PYQi)P)_UV%Y-wCaP2x78#AwmlV!FU z^}X(zp7d^GH_vADgmhyLbmQ+>9)4m>JqNrK$7fOjV%EvS1{uxwq*@Zcqq32v$-`Wy zMjO5Te~Zq}(LQXC%w)|S$~E%Rd|TNfTa!zs*C%|UjG0-cMT^ve(R`E99G$lB&~@72 zew=7#_(Z#m!_m`jR(>R&Xd82_aN9G#y65yIK?!+G`0Ew-Jf4WQ(2xG*3Bo_}{L=j! zS8m?;(o5+UtEYz*_6Q&U=hLJ5H4*mL!=QaTWPhjF^<=UP1(kwz#SU$9toy(4F3udg zH_6!jMb@#nc@bxXC7O*ZYqIZOTH9wY&(BI-ljNQ475>DL>O@O4g!9*Awg`?tnsBZt6-Ae z%=6H}+f#L|sQn|hhMbu*95?*&aKbS8W|j{c^9g>$N4_r}{*rVG+nkQ%wUySUGA9uN z*cN$Ghj38A7&2tCcxKO$BS-G_4b|~{-H`9z;75IzrMRg4UT#oGVn_O1<{~NqV?-UA zgW69zuSSEuOADd>i{QbS(3~{B5h)W9e~Gn(-5viE$w${WZ(VTlhpzkAZ7NusaK{;h zcB;2Vo7Tqr`<`>&+VC)^ZH(Q?{UIO}pO@fwlRrOwVD7pi|LR*$l3)8f%Q7qOSu-?e zAivq)Q0MOu)c=*p>6sjx$Y-#KXM#o2X$$ZpgtSc=5K-VmuWGjG5n?Rh->jg5Yx%YP2d`Mnx% zCuq);WQHz{2OT|xCUR2r+>Z48r|yX)INqmVchP+TS;K{d=72D5Ys>K#e5_v%a%m5JFGmpz!7Aw1u?GXrWyqV1&J zPgZuana0FcNs-GqTS0DCanA>;b62NhE<*4bn|UB5u{zK1N#S*lDqBWpeoAUyyfbTw zy`Uy7O)XP^ZbS39Xnc?QR?E?)6X0obj0sjks5K`f7-B%wfm#@Hpc>&|oeUHyR4kxk zj-v8B7}27H7jpx{7!l`>%?)9KNOIaUQQU40UqnhOKBe&m{P=QRnat`+*QZu2WEARI zj7pl%#>b~#5trw1>8wSb8B(T2u^d?|w{N_tYgWEJEtgj+Z&RO^$0WarId$k8#xUj; zhlOR_U8~6%j!T5!m0o%g{V_s(0vp4a#;gpxlrivox>?tTXv<@}w&oYlXi(b*w6PVd z{Feo=i2ohs32$x)z8%H(OKUBYx=# ziNu&9t&$nFF_o;{5lwau3a@>?zkmJXM2q+-{t6vlu#{Kvd`KT05oHgphw1>G0@G7y z6msy2;&ej7p8F5@*HB;-84ds*qC*(iBadcSP1^0EsagZXoyY1sT%6gIEZol-=9ON= zd1l#!kCXG7wWg8$(q&d9Ep~ibX|A4mi6U-}?FPmF**DnVw~yFgemx~)shJPtmu6QS z+E2{FLANWtzs}xN?8RqaHQR>Ns`U zpM4own=LEib{WhvwZ?esusTbhty5OAg6g_|d1xK8lcUV?!0qL+zH_~cY~udjb@Pkx&s zeh%rip$%)+ayP4PJ@e$LsstOnxXAI#g3bRJ%Z2?De3TV>aWT!Cq(1c`d_#$d&!Ijo z*e`ilSB4zxD1wGiM5>>dEQu|YB~5a>iwRU7D5?nP`JdoI*`RcIKyqlEbYz{Bl=IqX zO=4L>E@Dors)qS@&dNB-3+v)S34<63b@+f;q>Ss4kwaI(5d8+^zSo1Fs4?12$f zXY7uicD?d_3YoQY$xCHrOCw*w-!Txf+xXL`Uw@td^H25J&CS{Mce^YW!swa94?f7R z9Iy8*UhJtKUx~q#!iN(8`v3Hk&7W9T1`qQG7?0Wxv?r@Y%rjYxzLndf4p1(qi^n%M z4fgK@E7O<}4L49TDe{kz{HXteKCs84gEV4-hBy9f&^}z39!j*pUyRtGY)j)^H{LjW zguCe$ZV_7Ab?bWij|xk<>xV6Y9|MnY&*0}%!owKLv)5qCT{&u6JKLg=3!@zQl`3ha zkjF7s*fknLJi)IvIMr&E%KhStg=)3)=O~AcN4U+QUb~#z#c>y3%;AY4bJOSIkv=yw zGqZl~+-PqM9<}0+;NuNBnFc^=O)U@WQ*Jah#K?@b06hjbN8ZCV=U{M$t#CH}jE;Bw zgc^hx;UlEJSh>qNG#Z7y*+$-A4~qL8CEDpzFtzB5%3}%%2Mc$M4}Qpx!Q3VC9n9T$ zzNm-idoIGi48qSc6Zq;5HU|FdG@OX#DcJ#G zH;F~}b|Ca8`HiqBh;<`86)$GHJrcR4&!%A2Dm&2*43MqJ2H(Ge=U42Hw^-_^9;Wwk zo#^kep!3Sv6(uG?D}Y2`VNx$KJK=ADpgTJjS?pL453$JPk__BIfM|o)Xo=_}P8&lS zy!rHVW}Y8QO3eTElfwAKF;e~PBh0f_OVajM+F!nPdm;*0g2&gPhJxXF2H10zVOMs7 zSAuT4aq+q2x2NW|Z--5c`^0s4`q5G*~T)S?aEZcnzBkOeS&R z1mMelB;?Ds2L}F;W;90Ev>DphwDC{!i+!i#cFvfi`-}2lW6T>kw%y%4-%SfbYlIt& z##G@2D`9P;HmS6OOy_Ut=~}k6ykgo**YnJDZxjluyx_P7UjDpPhTuauTxPT8n7Ft4VfY@<+ zjIz!-U|wZf17=})UXiju*OlpV%XB*RhP~Q8enX!HE*0N;ytTR->w;#(z81p2eFye6 z2HuBB71CkTgQ|AwRy9wdbsAThpbe&ML;u>?fyE{?LEfk&mkRGGnfrymlU2fg@wRB# z08t*1C|o;EUp2%jt*h)xMx!xq=?nDs2KpEW?k2DZz20Su0=PSvfo#+?&?WV|v#?@u zXGUn;f5Rnk`sPi&w@G()|48>JdB@|<>K(SPTVznHgRiKV8G%QIy^$OHEx(EKi*1y| zy+b(Ri8QA}%9;E|CBtDHkE?HRu#b#eEb;sNI-P%Cm}yw#JzXx=LC1<6BG3AL!+B#O zt%|XZuzW6%rC6C z;DVtISM-gK_mO?k(Fq5Z^ZfDy@s{Ws$~!D_CXeFG-C`bZIprS@?anrZD8*`+l?tagO)AHZo z*iJ^ZNTkD>8cLTM+8a>#_#v~BV`VZ4#~GLEhEg4|GL43B`IGg)!rHj?u7aGU+Rje8 zhK!%@6X)6Rh~`3~O2dg1ay(PqJ%x`Z?J4!dFqD2E5~%;2tq;DaRbN?tu>3+&(0##> zL8nK9?C=8U?qWrjz!GvskhKY4yC1Ckhk|%D+_-vS-_G0a+Wg$_eepy^5IZl%yga0aLDykBGy;S%?$p#*Z((TdVi?L7L`oS3mr0S1Vgj8LY zpD@~XK@U8?(x}p(uZ0avWgPX@(K+x@XKPjeBkfbXJK-XrcglL|H#P)~!Wt$yr1u8Zv=0q;Vx;M0&*jqvl-S`)S`}LfuE0S#Yz#H8b+a^ z7xYD+JZPUjEjl{d9sH3=0m(*N+`+ruR;#T!yoib5vr5HeSc0GMgex+$+*bIXVe+Q4S1+tEkMNmKsH7V(fwjv_hmztp(H?f)>svbs!sM zaw? zG)2P_M}CZ}6l%!5^qUt}@kYO&ylyjZqsKD?AJ}fP%|M%;dUNoDmX?Pb8cvlX<2- z{PQDWTL8B!-dQNJ9sz+KGaN!VAIcCD7J|qaCF_6s6)=GC2}crzXE;_WJVc4;^7xUD zNpdB%3+~b|fl89HBfhWiNMCOszK(2R(&mno>Il)5j?7&IDVT>^s#`%fEV7mhsQynV zkWxApV>+O6X;QrC1f+R$=!`sxL8W*rc97l+`J?a{`35Zuo?G=h+em%O>~Y~H7pd$J z`Z;pOFt#$nn8YqT)<~I?>|K$(;-)^bDq~|zQd;GB(aITX`ldJEdZLb8>{|=rk(IVQ zckWu>Jdytrpfl+_+#+-qqE@yi6z&G!LHA8jiea|YgK8&RAaoB!Z|)TF61E&aeL=X# zU%9FKNl9vDg-N3wi1TD^*pTdYD=HSbR`?dH+Ul;1+c69aLR;@AA@hehYPKipzTCHAN%z6{j6HjjLZ`x7O(6q@vJluCN2;wYAz#3&y zvw_3tIfVKV|6AEe8F!L&jF^dVDujk)P+$&8B$MM@&dSCgUwDM)A0Z?OlnAwSO9;oL z1W4fTG-Ceu(35b*3;ZLG@Lw+CZz<;+$S&OATB@1W;!_KQ_G_<2AJe2dvUoA66W*;O zor~u3_cwrm@sPl;bdMNMq;)rq;i(c4O*K(;BMpgls)!1VG}I1z5q)l{Oo<3nien&& z&MJT@FjNX}G<80+3fvR!FZfpInUV5BDKNtm{Bo5sZE)*VM&Vz#Kl+{Nrky!i<%?35 zWfsJ2RUl8(VZuuaw3=y;!Q>}KloZTV={S}S;5CNj6q1_ z$!VzMYMvyBCp-$aze7t#qb>0yKA?vXy47S%_+&mI^FKiojElRvg#BKg--IzlapYc@ zS=-yFSDQecrz!@veSCCZ~bF=vd10>da8X0@o_u#|{|661pB3ekuJWfus6%r3Hj z{_YmDMiciFVi|Xntv?;+$X4ND4*&PO(lC~BK(-9~N4z0v3i`a8XN!qrFid_!~&j&qypU~!JkQaCIM5jbD;HT9@EssH5h|*1rZ1l>i!Vw z^GowP%*OL5KIRi)%~)A=;?a(`v}hQ}UBIcx146V_OGe@>mexh}u?_jL!Wwd!LAcaF zJ|uCtJKTI&&BX<>E+f8F_voB8ow2b_TW6deCa-XR6H+l-IFzt&NKfBm*Atzcyowby zWI^6F;0k5n{tD0!#e7~=1LgrqBXmPIUUdA0j(`@B{6cj_cD*n!)T;u0(&8LU80^7a z_5Kq+-xFkUFw1AA0By>K%QQsxNB*_fc=8EIDV8zR*ZBr~4SpYaC&eV1F$?jLAtQ#= z%_PAu8p?UnRmaah2~Pe?Dlm3@^rgx%pp7*cP8#LCE0UdtZHm-{6?Mfk`V&m zE$I>t{vvPk_g9&fN{L)f&BzYjFa}df)oQ6DHto?*x;B^XZC_bv*f5qK3GFzgA)CF-*ViWRje-FXZi5&1bBcA~B)>WU~h zA7i}#valUq<5K|v{*w8^A@1#%(U>zaqrW4m!Py*14n%n2p^&Jx2S|~BzW;&?@S~%n zV^eo`%}(6f0Bv|rQ&neyk4wTbhwd@0+Qd|%iQSvJs}T`2>BgxrI-d-pJ{9EX`GT+b ze2^OCS)RW_c#^~m?~y*?%7Ac{f((e>J>fA)F`{^6+89B2+Bn1qFX4$g@MM&4(1)*9 zp0>~D$#07iTM}FC_EtQl?7|yMO7N+XgfvWqNlwN-OX1r)EaJ`Sn5|ojUPwUV)M|qe z!WRwcg_31{QSL}1*Z+yFJ-PV!RAm8-Lpcr2-j_;o6wfG440poeieqZsQ2c>$X-iQfhM&&p7d5>qTj+)-%(Uxq_7!O}j#v-oi&{=!a>5%`Jv=i6~DQ8ag?q5MBa@*5NgH{;tV zoHlT@y3uZq$KJ@Irh^9Q%mr$Ddx6Jkk6tm-=RxFxP}wb7C=|!!S|+*Y!xLXbv+%rf zV%$3rCu|#x8z9F8#Te4LJ|F*lp~UMcT+nM@TwWlc_ZBZC!g?U+C2GXlujT{v$P32E zTO%VQ!LP3;Z=p}>HP`rrW7#ezE8#m)eVDnTq~uaquNmYt-n@4y`uOa{$LEN0FAe$d zk7GUa(e;W-X?nO~r!jgeqI(a&0n&-M4MM4c6L=>Kd&F@FQ;<$Xn6vYQf7n$ChAS`1 zYjfllv?LEzmd9%}$o3eY?z7J*EMdt_KRYkmFi^oTH@)fn>>`=`!e^b2?|fe-df%of zRr7R{JZ)xH!CI4NUVc&Ly6Fq5x+Uzw_1o2QZMR0Im|=_?ozv@{R#0Z(*5&zgR;v`% zSv`q!rc=c$c1zrnoX7KU3)39jE5$x*Y}9P5J&I=c7tyjPC{{RhkQWeMJ#`F5ra^^7 zK2*YgB67Am_$kLEBnA)h%uT|p(edvD>Q{MA_O8gAp}0i&Sf8r&DQ0a;HHglOsY*HL<&c5mGDPItO}b#Cp#p_%pZajQ%HT%xb(#3en;R$Y3HPJI?im=-%M^`eS*!$78bh3Hmoh_KTL>g#`W9ks&*3vDUJxDAyF~~$QP?Byz8c6 z^xSG5U!<|dvo5+pSf$lsGS)3RwV7XKpF4Zrbqo59)pO=m%q8#Pb6b}1{E{uWQZ3|c z26Ce4+;7_5r3AH!QAR*&tZ>4pAb9!92xZuZevV>BR?I|KXAn7j*#)A0heehI4mMqi z9Nzdk5&$^W2AHD2!+{qRZ{X(}!etQ`+1()(5f_k6&nTOjJ}enrySZ1oO!&3)j;1Ac zD+!QAwS?5Z{V>lzOxhFhXf4L0l_(*_$|!^_198p>ZfVX^D3yvVr!J$PQ>j!d+0OJ{ zr`wZ}SU7uj296hN&xU

!zuFRU%??dbG47GS2h-9)Ykef_W*F*| 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

6b;`fM-w{2GmQ?}y?qehJgk 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%0AJ#A=Kd0=LN4Le*?! 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%0DO1^1iRsXkzm6^|=X#0FjJmPd)SKe?$(l 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

f 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Ȋ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%0Drt(`>R3?U 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

@NDDCqnU-f~n>T6# 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'R4=i_`nl8czOQ`EF@(=G4TnXk#TZ)uICj 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 M%z)EiCM4hHBme*a literal 0 HcmV?d00001 diff --git a/tauri-app/src-tauri/icons/128x128.png b/tauri-app/src-tauri/icons/128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..6be5e50e9b9ae84d9e2ee433f32ef446495eaf3b GIT binary patch literal 3512 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 literal 0 HcmV?d00001 diff --git a/tauri-app/src-tauri/icons/128x128@2x.png b/tauri-app/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e81becee571e96f76aa5667f9324c05e5e7a4479 GIT binary patch literal 7012 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 Sh@|Ye~|0>}wMPLT8jm@Y!H33Sz}5aFI6 zM9Lzqz|;A*0sGs=2A1uU!1nk2dGF7knQwr99SAFen)x(eCO;F8y2C~0FD1YxRTPcy zPWVxkUYmeuz}Tv?7&Fe-!UE{)ZW)Mb;H)^#eHDv$`dkZGguJz@^MA!ZNGAUqt{|0H zpZ7Ch9S`q5!>R%}>}62!+(T^evyO+ImSo2wpu)su4^3nw5(%)KD%gbSev^*HZZ&3( z#&c@Z0gH|}Ck)w6fh0&NBJ62ib%R}(3@$VFl*_#l2W$wQ-~4RmZZAt5O*^2Q5}Xr8Hy@c`#pM?kc?hFWxRXr*mUfUCXf4ka5DD~ zat6d85COB05l#(P9*cQZ3EC8fVdS~?&vN#rce(aF9@xp80O2{{FBvU+{X>Hoh;xI` z{$e^Nw1y*VbO8wv`8|-m?NwNaKGTGaF{P^JLB^DbOYWIbn%eT`*!^C1H36=O8Z-M> zkD~88ry`eSo`tEBN4>w7OWZwUzlh{WM1m8R6zepqGcGMaV7vWY9b?K4b6~|HVG)ec wi>I@ws#sZo7or4_*4M>7;p5{nr2pZ?Uu4>Krr0kU)&Kwi07*qoM6N<$f)&@lf&c&j literal 0 HcmV?d00001 diff --git a/tauri-app/src-tauri/icons/Square107x107Logo.png b/tauri-app/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0ca4f27198838968bd60ed7d371bfa23496b7fe5 GIT binary patch literal 2863 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 Y2S8W#vM)6=T>t<807*qoM6N<$f*y@n<^TWy literal 0 HcmV?d00001 diff --git a/tauri-app/src-tauri/icons/Square284x284Logo.png b/tauri-app/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c021d2ba76619c08969ab688db3b27f29257aa6f GIT binary patch literal 7737 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 literal 0 HcmV?d00001 diff --git a/tauri-app/src-tauri/icons/Square30x30Logo.png b/tauri-app/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..621970023096ed9f494ba18ace15421a45cd65fa GIT binary patch literal 903 zcmV;219<$2P)2 z+CUKPMqaqGiH;zb!R4$B-WXS^YzQr=@UH>k4?*L)&R=zYjBrZenKdc9|JlS$SO*RJ zKt8FSTDAdk1g_WPAO!p^V!AuL;Lm;uQyV;zKq)J3i(;q*;k+pD%f3eltU`PYdy9(k0&%` zuWAPcV6|-y?|?7O1W!KSK}pbk8#~!|FA@(VJkt^V@0lio{afoAeo*f&$W2s6${5!1eKvAGD2$GZwSB98L2ZVS- zKn8ENRkZ*sb!@QugOrQNK3(sy1v%J#m|rpB+h|Nkqa3FRT>74xSs{#&saU2Lf!_Iq zKmuKAESh`gs!fneGWn+nf}l?7jE$HW!Af&vE5=G!QU)U2v&HLIBGXKk4nQx{hsHjL zLPMAo5=*uInFbq7(aa`Y2VX5wCmaeqvECOFv)a>0t>ZaEb*cJccER=BB?KFZhV$c^ znL*l8x*UYZv4WK|j?~Jt6~~F%{pk~z5A*>^M`?r5m9@RJ_x|uEtX(6Vk@Y()MVto* z93wr)%3m%|#OZ~srm>zF(JvDuTq*@;d&^>_BJm5hOU`3FjG70L#Vzv9I?`<7$T@

jU?lMi@tgxr7CqX_r3uw^y4tVU3Pm0sw;|1WSUO%?=bG`*Kmz6u4{#ti;T7AWIBAEh!(Y zz>O01&#X?Ds@L)Sb{CkG#Yz4$3o d@96)?#cz^xWoA}>B$xmI002ovPDHLkV1l3&k#zt7 literal 0 HcmV?d00001 diff --git a/tauri-app/src-tauri/icons/Square310x310Logo.png b/tauri-app/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f9bc04839491e66c07b16ab03743c0c53b4109cc GIT binary patch literal 8591 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 JPDHLkV1lm|RXG3v literal 0 HcmV?d00001 diff --git a/tauri-app/src-tauri/icons/Square71x71Logo.png b/tauri-app/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..63440d7984936a9caa89275928d8dce97e4d033b GIT binary patch literal 2011 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∓t$dRJGIadE?vc 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 Kx8Vjl{{H|h@<;Lj literal 0 HcmV?d00001 diff --git a/tauri-app/src-tauri/icons/icon.ico b/tauri-app/src-tauri/icons/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b3636e4b22ba65db9061cd60a77b02c92022dfd6 GIT binary patch literal 86642 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 AKmY&$ literal 0 HcmV?d00001 diff --git a/tauri-app/src-tauri/icons/icon.png b/tauri-app/src-tauri/icons/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e1cd2619e0b5ec089cbba5ec7b03ddf2b1dfceb6 GIT binary patch literal 14183 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

5l#v3A#xI3*z~1~fvo9Q(N^(==!|_FZ z*duZ=+M1~)8E|otX8KNZlr?qels#x_1Xq@9IIw~@9uAREJVH)Xw^}UclF6327}E42 zT)E&?U%TK?(+K7%R!`H5oX0i)4Qn5??Iw3p5J~6_u+aWehY{DSn}3V2p$bgjnAu?o)v@iC254fXeMv50$9YrpU`N?u@QIWs)T?SP|fa}(|9 zqAX+!7`cx=4)cCBg5h~pu(?@9`)aCr#oyz$ld=#RFxYCNZCZls@4v2~*e-t6PEVvV z&bbK3b3wt(Coc!ufAbXXC<**#HQ%J9k`New6iG<5RjtO4XVO?dCvwxD{kJ#tfQr(X zg^NTwF-FwAeS_{V4bfel8l`~NbfrTR2s!G>WduFWxH(t~aK4q=6rEE^$+Uox>gJO2 z{L<;6Q6nHa5#ZEM>H58not!)z(6*_=^~8}jWf*IG$AUKVWOZ4?)GfF z+BM#*wKKmLFD7E~W3U!$IVm$k_k1f&Kz6WV8@55P?r~bcg-Za-!rvW?ns&)KOGT2~ zlkAyqhQj=P$Eg3w#K~}zH@J5bo-BfHjInKSz$@?+Z)NPD4pHj^_Qxmi`UqoTy=`sV zLVxrXGuBr=QRm|}wg75yetQQK4fY3#P_~J}zEfPnb2C4Wo!E(d*(cA;b?7$g2in<( zPn)ghX}nzJPmb6(3Dpeg_GW~Hc}Lt=lgsSZz z!5QXyz7KaR;D`3Ee}d`af{H>WWZ|Io1QI3~4Ll_`g1(cRnhLK73Ro)7zPCd={1W2x zRp%Xlvv4>!<2@}$hz|!V{T}_eHx2xkLl^hQoZTCnsjCl|W_@5Fx2(+j0ogy&Y+;L- z<)G$*CiN7hOm^s!{U>1F7U=iNk{+u~dAC!eDz%=|glFW0jEZU1&o(G_c#wTxUjnG} z#cg3>jEpUi#Mlq@t?Msg_#geK^Lx@DyHWf7=AS5vVyM7YOjvUVCfcpVR<(+5!H?9- zySI6s>o3m&*zr||=wcPGyBkQV`EWJl@bH8qobjOp+sXL*)=&yX)8aAbf~tGv?a2SN zu^Ddo-z?DWk9h9Yz#5p^NU#x~wYSd?H@w@!2Gb4G)6-utEMV~~M85Br5ff(v5O1|T z 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 (HTTP方法, API路径) +pub fn get_route(module: &str, action: &str) -> Option<(&'static str, &'static str)> { + match (module, action) { + // 脚本相关 + ("script", "generate") => Some(("POST", "/script/generate")), + ("script", "polish") => Some(("POST", "/script/polish")), + // 语音相关 + ("voice", "generateAudio") => Some(("POST", "/voice/generate")), + ("voice", "getVoiceLibrary") => Some(("GET", "/voice/library")), + // 视频相关 + ("video", "generateVideo") => Some(("POST", "/video/generate")), + ("video", "getDigitalHumans") => Some(("GET", "/video/library")), + _ => None, + } +} + +/// 通用 Python API 代理请求 +async fn proxy_to_python( + method: &str, + path: &str, + payload: serde_json::Value, + default_data: serde_json::Value, +) -> ApiResponse { + let url = format!("{}{}", super::PYTHON_API_BASE_URL, path); + let client = reqwest::Client::new(); + + let request = match method { + "GET" => client.get(&url), + "POST" => client.post(&url).json(&payload), + "PUT" => client.put(&url).json(&payload), + "DELETE" => client.delete(&url), + _ => client.post(&url).json(&payload), + }; + + let response = match request.send().await { + Ok(res) => res, + Err(e) => { + return ApiResponse { + code: 500, + message: format!("Failed to call Python API: {}", e), + data: None, + } + } + }; + + if response.status().is_success() { + let data: serde_json::Value = response.json().await.unwrap_or(default_data); + ApiResponse { + code: 200, + message: "success".to_string(), + data: Some(data), + } + } else { + let status = response.status(); + let mut error_message = format!("Python API returned error: {}", status); + if let Ok(text) = response.text().await { + if !text.is_empty() { + error_message = format!("{} - {}", error_message, text); + } + } + ApiResponse { + code: status.as_u16() as i32, + message: error_message, + data: None, + } + } +} + +/// 通用 API 调用入口(Tauri 命令) +#[tauri::command] +pub async fn api_call(app: tauri::AppHandle, req: ApiRequest) -> ApiResponse { + println!("API Proxy: module={}, action={}", req.module, req.action); + let _project_dir = utils::get_project_dir(&app); + + // 检查是否是路由表中定义的端点 + match get_route(&req.module, &req.action) { + Some((method, path)) => { + // 根据 action 确定默认返回值 + let default_data = match req.action.as_str() { + "getVoiceLibrary" | "getDigitalHumans" | "generate" => serde_json::json!([]), + _ => serde_json::json!({}), + }; + proxy_to_python(method, path, req.payload, default_data).await + } + None => { + // 未实现的端点 - 明确返回 Mock 标记 + println!("Warning: No handler for {}:{}, returning mock response", req.module, req.action); + ApiResponse { + code: 200, + message: format!("success (MOCK - {}:{} not implemented)", req.module, req.action), + data: Some(serde_json::json!({ + "_mock": true, + "_module": req.module, + "_action": req.action, + "_note": "This endpoint returns mock data for development" + })), + } + } + } +} diff --git a/tauri-app/src-tauri/src/auth.rs b/tauri-app/src-tauri/src/auth.rs new file mode 100644 index 0000000..c082df5 --- /dev/null +++ b/tauri-app/src-tauri/src/auth.rs @@ -0,0 +1,25 @@ +//! 认证相关命令 + +use serde_json; +use crate::ApiResponse; + +/// Mock 登录接口(开发用) +#[tauri::command] +pub fn auth_login(phone: String, code: String) -> ApiResponse { + println!("Auth Login: phone={}, code={}", phone, code); + // Mock successful login - 明确标记为 Mock 数据 + ApiResponse { + code: 200, + message: "success (MOCK login - no real authentication)".to_string(), + data: Some(serde_json::json!({ + "token": "mock_token_placeholder", + "user": { + "id": "mock_user_001", + "nickname": "创作达人", + "avatar": "" + }, + "_mock": true, + "_note": "This is mock authentication data for development only" + })), + } +} diff --git a/tauri-app/src-tauri/src/avatar_cache.rs b/tauri-app/src-tauri/src/avatar_cache.rs new file mode 100644 index 0000000..2437e1f --- /dev/null +++ b/tauri-app/src-tauri/src/avatar_cache.rs @@ -0,0 +1,110 @@ +//! 形象克隆本地缓存模块 +//! +//! 负责将远程视频和封面缓存到本地文件系统,加速卡片加载。 +//! 存储逻辑已迁移到 storage::cache,本模块保留命令入口和下载逻辑。 + +use tauri::AppHandle; +use reqwest::Client; +use base64::engine::general_purpose; +use base64::Engine as _; +use crate::storage::cache::CacheState; + +pub use crate::storage::cache::{CacheQueryResult, CacheSaveResult, CacheDeleteResult}; + +/// 查询指定 avatar 是否已有缓存 +#[tauri::command] +pub async fn query_avatar_cache( + app: AppHandle, + state: tauri::State<'_, CacheState>, + avatar_id: String, +) -> Result { + crate::storage::cache::query_avatar_cache(&app, &state, &avatar_id) + .map_err(|e| e.to_string()) +} + +/// 从远程 URL 下载视频并缓存到本地 +#[tauri::command] +pub async fn cache_avatar_video( + app: AppHandle, + state: tauri::State<'_, CacheState>, + avatar_id: String, + remote_url: String, +) -> Result { + let client = get_http_client(); + let response = client + .get(&remote_url) + .send() + .await + .map_err(|e| format!("Download failed: {}", e))?; + + // 检查 Content-Length,防止过大响应 + if let Some(content_length) = response.content_length() { + const MAX_SIZE: u64 = 200 * 1024 * 1024; // 200MB + if content_length > MAX_SIZE { + return Err(format!( + "Video too large: {} MB", + content_length / (1024 * 1024) + )); + } + } + + let bytes = response + .bytes() + .await + .map_err(|e| format!("Read failed: {}", e))?; + + crate::storage::cache::save_cached_video(&app, &state, &avatar_id, &remote_url, &bytes) + .map_err(|e| e.to_string()) +} + +/// 保存封面图片(base64 格式)到缓存 +#[tauri::command] +pub async fn save_avatar_poster( + app: AppHandle, + state: tauri::State<'_, CacheState>, + avatar_id: String, + base64_data: String, +) -> Result { + // 支持任意 data:[mime];base64, 前缀 + let base64_clean = base64_data + .strip_prefix("data:") + .and_then(|s| s.split_once(";base64,")) + .map(|(_, b64)| b64) + .unwrap_or(&base64_data); + + let decoded = general_purpose::STANDARD + .decode(base64_clean) + .map_err(|e| format!("Base64 decode failed: {}", e))?; + + crate::storage::cache::save_cached_poster(&app, &state, &avatar_id, &decoded) + .map_err(|e| e.to_string()) +} + +/// 删除指定 avatar 的缓存 +#[tauri::command] +pub async fn delete_avatar_cache( + app: AppHandle, + state: tauri::State<'_, CacheState>, + avatar_id: String, +) -> Result { + crate::storage::cache::delete_avatar_cache(&app, &state, &avatar_id) + .map_err(|e| e.to_string()) +} + +/// 获取缓存统计信息 +#[tauri::command] +pub async fn get_cache_stats( + app: AppHandle, + state: tauri::State<'_, CacheState>, +) -> Result { + crate::storage::cache::get_cache_stats(&app, &state) + .map_err(|e| e.to_string()) +} + +/// 获取或创建全局 HTTP 客户端 +fn get_http_client() -> Client { + Client::builder() + .timeout(std::time::Duration::from_secs(60)) + .build() + .unwrap_or_else(|_| Client::new()) +} diff --git a/tauri-app/src-tauri/src/commands/asset.rs b/tauri-app/src-tauri/src/commands/asset.rs new file mode 100644 index 0000000..5cb6ef5 --- /dev/null +++ b/tauri-app/src-tauri/src/commands/asset.rs @@ -0,0 +1,62 @@ +//! 项目资源存储命令 + +use crate::ApiResponse; +use crate::storage::project as project_storage; + +#[tauri::command] +pub async fn save_project_asset( + project_id: String, + filename: String, + base64_data: String, +) -> ApiResponse { + match project_storage::save_project_asset(&project_id, &filename, &base64_data).map_err(|e| e.to_string()) { + Ok(path) => ApiResponse { + code: 200, + message: "资源保存成功".to_string(), + data: Some(path), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("保存资源失败: {}", e), + data: None, + }, + } +} + +#[tauri::command] +pub async fn get_video_save_path( + project_id: String, + filename: String, +) -> ApiResponse { + match project_storage::get_video_save_path(&project_id, &filename).map_err(|e| e.to_string()) { + Ok(path) => ApiResponse { + code: 200, + message: "获取路径成功".to_string(), + data: Some(path.to_string_lossy().to_string()), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("获取路径失败: {}", e), + data: None, + }, + } +} + +#[tauri::command] +pub async fn get_image_save_path( + project_id: String, + filename: String, +) -> ApiResponse { + match project_storage::get_image_save_path(&project_id, &filename).map_err(|e| e.to_string()) { + Ok(path) => ApiResponse { + code: 200, + message: "获取路径成功".to_string(), + data: Some(path.to_string_lossy().to_string()), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("获取路径失败: {}", e), + data: None, + }, + } +} diff --git a/tauri-app/src-tauri/src/commands/auth_state.rs b/tauri-app/src-tauri/src/commands/auth_state.rs new file mode 100644 index 0000000..6ef6b50 --- /dev/null +++ b/tauri-app/src-tauri/src/commands/auth_state.rs @@ -0,0 +1,55 @@ +//! 认证状态存储命令 + +use crate::ApiResponse; +use crate::storage::auth as auth_storage; + +#[tauri::command] +pub async fn load_auth_state(app: tauri::AppHandle) -> ApiResponse> { + match auth_storage::load_auth_state(&app).map_err(|e| e.to_string()) { + Ok(data) => ApiResponse { + code: 200, + message: "Auth state loaded successfully".to_string(), + data: Some(data), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to load auth state: {}", e), + data: None, + }, + } +} + +#[tauri::command] +pub async fn save_auth_state( + app: tauri::AppHandle, + state: serde_json::Value, +) -> ApiResponse { + match auth_storage::save_auth_state(&app, &state).map_err(|e| e.to_string()) { + Ok(_) => ApiResponse { + code: 200, + message: "Auth state saved successfully".to_string(), + data: Some(true), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to save auth state: {}", e), + data: None, + }, + } +} + +#[tauri::command] +pub async fn clear_auth_state(app: tauri::AppHandle) -> ApiResponse { + match auth_storage::clear_auth_state(&app).map_err(|e| e.to_string()) { + Ok(_) => ApiResponse { + code: 200, + message: "Auth state cleared successfully".to_string(), + data: Some(true), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to clear auth state: {}", e), + data: None, + }, + } +} diff --git a/tauri-app/src-tauri/src/commands/avatar.rs b/tauri-app/src-tauri/src/commands/avatar.rs new file mode 100644 index 0000000..d8b9a07 --- /dev/null +++ b/tauri-app/src-tauri/src/commands/avatar.rs @@ -0,0 +1,39 @@ +//! 克隆形象列表存储命令 + +use crate::ApiResponse; +use crate::storage::avatar as avatar_storage; + +#[tauri::command] +pub async fn load_avatars_list(_app: tauri::AppHandle) -> ApiResponse> { + match avatar_storage::load_avatars_list().map_err(|e| e.to_string()) { + Ok(data) => ApiResponse { + code: 200, + message: "Avatars loaded successfully".to_string(), + data: Some(data), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to load avatars: {}", e), + data: None, + }, + } +} + +#[tauri::command] +pub async fn save_avatars_list( + _app: tauri::AppHandle, + avatars: Vec, +) -> ApiResponse { + match avatar_storage::save_avatars_list(&avatars).map_err(|e| e.to_string()) { + Ok(_) => ApiResponse { + code: 200, + message: "Avatars saved successfully".to_string(), + data: Some(true), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to save avatars: {}", e), + data: None, + }, + } +} diff --git a/tauri-app/src-tauri/src/commands/mod.rs b/tauri-app/src-tauri/src/commands/mod.rs new file mode 100644 index 0000000..dae93b7 --- /dev/null +++ b/tauri-app/src-tauri/src/commands/mod.rs @@ -0,0 +1,11 @@ +//! Tauri 命令模块 +//! +//! 所有前端 IPC 调用入口按领域拆分,参数通过 Args 结构体接收。 +//! Args 结构体标注 `#[serde(rename_all = "camelCase")]` 以匹配前端 JSON 字段名, +//! 函数内部使用 snake_case 变量,消除 Rust `non_snake_case` 警告。 + +pub mod asset; +pub mod auth_state; +pub mod avatar; +pub mod product; +pub mod project; diff --git a/tauri-app/src-tauri/src/commands/product.rs b/tauri-app/src-tauri/src/commands/product.rs new file mode 100644 index 0000000..605e699 --- /dev/null +++ b/tauri-app/src-tauri/src/commands/product.rs @@ -0,0 +1,295 @@ +//! 成品保存命令 + +use crate::ApiResponse; +use crate::storage; +use crate::storage::project as project_storage; +use crate::storage::get_products_dir; +use std::fs; +use serde::Serialize; +use std::path::PathBuf; +use tauri::AppHandle; + +#[derive(Serialize)] +pub struct ProductItem { + pub filename: String, + pub path: String, + pub created_at: u64, + pub file_size: u64, + pub poster_path: Option, +} + +#[tauri::command] +pub async fn save_final_product( + project_id: String, + source_path: String, + product_filename: String, +) -> ApiResponse { + match project_storage::save_final_product_to_products( + &project_id, + &source_path, + &product_filename, + ) { + Ok(target_path) => ApiResponse { + code: 200, + message: "成品保存成功".to_string(), + data: Some(target_path), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("保存成品失败: {}", e), + data: None, + }, + } +} + +#[tauri::command] +pub async fn list_local_products(app: AppHandle) -> ApiResponse> { + match get_products_dir() { + Ok(products_dir) => { + let products_dir: PathBuf = products_dir; + if !products_dir.exists() { + return ApiResponse { + code: 200, + message: "成功".to_string(), + data: Some(vec![]), + }; + } + + let read_dir = match fs::read_dir(&products_dir) { + Ok(rd) => rd, + Err(_) => { + return ApiResponse { + code: 200, + message: "成功".to_string(), + data: Some(vec![]), + }; + } + }; + + let mut products = Vec::new(); + + for entry in read_dir { + let entry: fs::DirEntry = match entry { + Ok(e) => e, + Err(_) => continue, + }; + + let path: PathBuf = entry.path(); + if !path.is_file() { + continue; + } + + // 只处理视频文件 + let ext: Option = path.extension() + .and_then(|e: &std::ffi::OsStr| e.to_str()) + .map(|e: &str| e.to_lowercase()); + match ext { + Some(ext) if ["mp4", "mov", "avi", "mkv", "webm"].contains(&ext.as_str()) => {} + _ => continue, + } + + let filename: String = entry.file_name().to_string_lossy().to_string(); + let path_str: String = path.to_string_lossy().to_string(); + + let metadata: fs::Metadata = match entry.metadata() { + Ok(md) => md, + Err(_) => continue, + }; + + // 获取创建时间,如果失败则使用修改时间 + let created_at_sys: std::time::SystemTime = match metadata.created() { + Ok(ct) => ct, + Err(_) => match metadata.modified() { + Ok(mt) => mt, + Err(_) => std::time::SystemTime::UNIX_EPOCH, + } + }; + + let created_at: u64 = created_at_sys + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; + + let file_size: u64 = metadata.len(); + + // 封面图路径:{products_dir}/{filename}.jpg + let poster_filename = format!("{}.jpg", filename); + let poster_path = products_dir.join(&poster_filename); + let poster_path_str: Option = if poster_path.exists() { + Some(poster_path.to_string_lossy().to_string()) + } else { + // 封面不存在,使用 FFmpeg 提取第一帧 + match crate::ffmpeg_cmd::extract_first_frame(&app, &path_str, poster_path.to_string_lossy().as_ref()).await { + Ok(_) => Some(poster_path.to_string_lossy().to_string()), + Err(_) => None, + } + }; + + products.push(ProductItem { + filename, + path: path_str, + created_at, + file_size, + poster_path: poster_path_str, + }); + } + + // 按创建时间倒序排序,最新的在前 + products.sort_by(|a, b| b.created_at.cmp(&a.created_at)); + + ApiResponse { + code: 200, + message: "成功".to_string(), + data: Some(products), + } + } + Err(e) => ApiResponse { + code: 500, + message: format!("获取成品列表失败: {}", e), + data: None, + }, + } +} + +#[tauri::command] +pub async fn delete_local_product(filename: String) -> ApiResponse<()> { + match get_products_dir() { + Ok(products_dir) => { + let products_dir: PathBuf = products_dir; + let path: PathBuf = products_dir.join(filename); + if path.exists() && path.is_file() { + match fs::remove_file(path) { + Ok(_) => ApiResponse { + code: 200, + message: "删除成功".to_string(), + data: Some(()), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("删除失败: {}", e), + data: None, + }, + } + } else { + ApiResponse { + code: 404, + message: "文件不存在".to_string(), + data: None, + } + } + } + Err(e) => ApiResponse { + code: 500, + message: format!("删除成品失败: {}", e), + data: None, + }, + } +} + +#[tauri::command] +pub async fn rename_local_product(old_filename: String, new_filename: String) -> ApiResponse<()> { + match get_products_dir() { + Ok(products_dir) => { + let products_dir: PathBuf = products_dir; + let old_path: PathBuf = products_dir.join(&old_filename); + let new_path: PathBuf = products_dir.join(&new_filename); + + if !old_path.exists() || !old_path.is_file() { + return ApiResponse { + code: 404, + message: "原文件不存在".to_string(), + data: None, + }; + } + + if new_path.exists() { + return ApiResponse { + code: 400, + message: "文件名已存在".to_string(), + data: None, + }; + } + + // 检查新文件名扩展名 + let old_ext = old_path.extension() + .and_then(|e| e.to_str()) + .map(|e| e.to_lowercase()); + let new_ext = new_path.extension() + .and_then(|e| e.to_str()) + .map(|e| e.to_lowercase()); + + // 如果新文件名没有扩展名,自动补全 + let final_new_path = if new_ext != old_ext && old_ext.is_some() { + new_path.with_extension(old_ext.unwrap()) + } else { + new_path + }; + + // 尝试从旧文件名提取 project_id(格式:final_proj_{project_id}_...) + if let Some(project_id) = extract_project_id_from_filename(&old_filename) { + // 更新项目元数据中的 finalVideoPath + if let Ok(project_dir) = storage::get_project_dir(&project_id) { + let meta_path = project_dir.join("meta.json"); + if meta_path.exists() { + let _ = storage::with_file_lock(&meta_path, || { + let mut meta = project_storage::load_project_meta(&project_id)?; + if let Some(obj) = meta.as_object_mut() { + obj.insert( + "finalVideoPath".to_string(), + serde_json::json!(final_new_path.to_string_lossy().to_string()) + ); + } + storage::atomic_write_json(&meta_path, &meta) + }); + } + } + } + + // 重命名视频文件 + match fs::rename(old_path, &final_new_path) { + Ok(_) => { + ApiResponse { + code: 200, + message: "重命名成功".to_string(), + data: Some(()), + } + }, + Err(e) => ApiResponse { + code: 500, + message: format!("重命名失败: {}", e), + data: None, + }, + } + } + Err(e) => ApiResponse { + code: 500, + message: format!("重命名成品失败: {}", e), + data: None, + }, + } +} + +/// 从成品文件名提取 project_id +/// 格式:final_proj_{project_id}_{random}_{timestamp}.mp4 +fn extract_project_id_from_filename(filename: &str) -> Option { + let without_ext = filename.rsplit('.').next().unwrap_or(filename); + + // 文件名以 "final_proj_" 开头 + if !without_ext.starts_with("final_proj_") { + return None; + } + + let parts: Vec<&str> = without_ext.split('_').collect(); + if parts.len() >= 3 { + // parts[0] = "final", parts[1] = "proj", parts[2] = project_id + // 但 project_id 本身可能包含下划线,所以需要不同的策略: + // "final_proj_" 长度是 11,之后第一个下划线之前就是 project_id + let after_prefix = &without_ext[11..]; + if let Some(next_underscore) = after_prefix.find('_') { + let project_id = &after_prefix[0..next_underscore]; + return Some(project_id.to_string()); + } + } + + None +} diff --git a/tauri-app/src-tauri/src/commands/project.rs b/tauri-app/src-tauri/src/commands/project.rs new file mode 100644 index 0000000..02b8d11 --- /dev/null +++ b/tauri-app/src-tauri/src/commands/project.rs @@ -0,0 +1,137 @@ +//! 项目存储命令 + +use crate::ApiResponse; +use crate::storage::project as project_storage; + +// ============================================================ +// 命令函数 +// ============================================================ + +#[tauri::command] +pub async fn save_project_meta(project_id: String, data: serde_json::Value) -> ApiResponse { + match project_storage::save_project_meta(&project_id, &data).map_err(|e| e.to_string()) { + Ok(_) => ApiResponse { + code: 200, + message: "Project saved successfully".to_string(), + data: Some(true), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to save project: {}", e), + data: None, + }, + } +} + +#[tauri::command] +pub async fn save_project_meta_raw(project_id: String, json_content: String) -> ApiResponse { + match project_storage::save_project_meta_raw(&project_id, &json_content).map_err(|e| e.to_string()) { + Ok(_) => ApiResponse { + code: 200, + message: "Project saved successfully".to_string(), + data: Some(true), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to save project: {}", e), + data: None, + }, + } +} + +#[tauri::command] +pub async fn load_project_meta(project_id: String) -> ApiResponse { + match project_storage::load_project_meta(&project_id).map_err(|e| e.to_string()) { + Ok(data) => ApiResponse { + code: 200, + message: "Project loaded successfully".to_string(), + data: Some(data), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to load project: {}", e), + data: None, + }, + } +} + +#[tauri::command] +pub async fn save_project_segments(project_id: String, segments: serde_json::Value) -> ApiResponse { + match project_storage::save_project_segments(&project_id, &segments).map_err(|e| e.to_string()) { + Ok(_) => ApiResponse { + code: 200, + message: "Segments saved successfully".to_string(), + data: Some(true), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to save segments: {}", e), + data: None, + }, + } +} + +#[tauri::command] +pub async fn save_project_segments_raw(project_id: String, json_content: String) -> ApiResponse { + match project_storage::save_project_segments_raw(&project_id, &json_content).map_err(|e| e.to_string()) { + Ok(_) => ApiResponse { + code: 200, + message: "Segments saved successfully".to_string(), + data: Some(true), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to save segments: {}", e), + data: None, + }, + } +} + +#[tauri::command] +pub async fn load_project_segments(project_id: String) -> ApiResponse { + match project_storage::load_project_segments(&project_id).map_err(|e| e.to_string()) { + Ok(data) => ApiResponse { + code: 200, + message: "Segments loaded successfully".to_string(), + data: Some(data), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to load segments: {}", e), + data: None, + }, + } +} + +#[tauri::command] +pub async fn list_local_projects() -> ApiResponse> { + match project_storage::list_projects().map_err(|e| e.to_string()) { + Ok(projects) => ApiResponse { + code: 200, + message: "Projects listed successfully".to_string(), + data: Some(projects), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to list projects: {}", e), + data: Some(vec![]), + }, + } +} + +#[tauri::command] +pub async fn delete_local_project(project_id: String) -> ApiResponse { + match project_storage::delete_project(&project_id).map_err(|e| e.to_string()) { + Ok(_) => ApiResponse { + code: 200, + message: "Project deleted successfully".to_string(), + data: Some(true), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to delete project: {}", e), + data: None, + }, + } +} + diff --git a/tauri-app/src-tauri/src/ffmpeg_cmd.rs b/tauri-app/src-tauri/src/ffmpeg_cmd.rs new file mode 100644 index 0000000..a697e4a --- /dev/null +++ b/tauri-app/src-tauri/src/ffmpeg_cmd.rs @@ -0,0 +1,332 @@ +use tauri_plugin_shell::ShellExt; +use tauri_plugin_shell::process::CommandEvent; +use tauri::{AppHandle, Emitter}; + +/** + * 封装 FFmpeg Sidecar 调用 + */ +pub async fn run_ffmpeg(app: &AppHandle, args: Vec) -> Result { + let sidecar_command = app.shell().sidecar("ffmpeg") + .map_err(|e| format!("Failed to find ffmpeg sidecar: {}", e))?; + + let (mut rx, _child) = sidecar_command + .args(args) + .spawn() + .map_err(|e| format!("Failed to spawn ffmpeg: {}", e))?; + + let mut output = String::new(); + while let Some(event) = rx.recv().await { + match event { + CommandEvent::Stdout(line) => { + output.push_str(&String::from_utf8_lossy(&line)); + } + CommandEvent::Stderr(line) => { + let log = String::from_utf8_lossy(&line); + println!("FFmpeg Log: {}", log); + + // 尝试解析进度: time=00:00:05.12 + if let Some(pos) = log.find("time=") { + let time_part = &log[pos + 5..].split_whitespace().next().unwrap_or(""); + if !time_part.is_empty() { + // 发送进度事件到前端 + let _ = app.emit("ffmpeg-progress", time_part); + } + } + } + CommandEvent::Terminated(status) => { + match status.code { + Some(0) => return Ok(output), + Some(code) => return Err(format!("FFmpeg exited with status {}", code)), + None => return Err("FFmpeg terminated by signal".to_string()), + } + } + _ => {} + } + } + Ok(output) +} + +/** + * 标准化单个视频片段 (调整为 1080:1920, 30fps, libx264, aac 44100Hz stereo) + */ +pub async fn standardize_video(app: &AppHandle, input_path: &str, output_path: &str) -> Result<(), String> { + let args = vec![ + "-i".to_string(), input_path.to_string(), + "-vf".to_string(), "fps=30,scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,format=yuv420p".to_string(), + "-c:v".to_string(), "libx264".to_string(), + "-c:a".to_string(), "aac".to_string(), + "-ar".to_string(), "44100".to_string(), + "-ac".to_string(), "2".to_string(), + "-preset".to_string(), "veryfast".to_string(), + "-crf".to_string(), "23".to_string(), + "-r".to_string(), "30".to_string(), + "-y".to_string(), + output_path.to_string() + ]; + run_ffmpeg(app, args).await.map(|_| ()) +} + +/** + * 拼接视频 - 快速模式 (要求编码/分辨率一致) + */ +pub async fn concat_videos_copy(app: &AppHandle, list_path: &str, output_path: &str) -> Result<(), String> { + let args = vec![ + "-f".to_string(), "concat".to_string(), + "-safe".to_string(), "0".to_string(), + "-i".to_string(), list_path.to_string(), + "-c".to_string(), "copy".to_string(), + "-y".to_string(), + output_path.to_string() + ]; + run_ffmpeg(app, args).await.map(|_| ()) +} + +/** + * 拼接视频 - 兼容模式 (多步走:标准化 -> 拼接) + */ +pub async fn concat_videos_robust(app: &AppHandle, video_paths: Vec, output_path: &str) -> Result<(), String> { + let temp_dir = std::env::temp_dir(); + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis(); + + let mut standardized_paths = Vec::new(); + + // 1. 标准化每个片段 + for (i, path) in video_paths.iter().enumerate() { + let std_path = temp_dir.join(format!("std_{}_{}.mp4", timestamp, i)); + standardize_video(app, path, std_path.to_str().unwrap()).await?; + standardized_paths.push(std_path); + } + + // 2. 生成 concat 列表 + let list_path = temp_dir.join(format!("concat_list_{}.txt", timestamp)); + let mut list_content = String::new(); + for path in &standardized_paths { + list_content.push_str(&format!("file '{}'\n", path.to_str().unwrap())); + } + std::fs::write(&list_path, list_content).map_err(|e| e.to_string())?; + + // 3. 执行快速拼接 + let concat_res = concat_videos_copy(app, list_path.to_str().unwrap(), output_path).await; + + // 4. 清理临时文件 + for path in standardized_paths { + let _ = std::fs::remove_file(path); + } + let _ = std::fs::remove_file(list_path); + + concat_res +} + +/** + * 音画合并 - 优化音质 + */ +pub async fn add_audio_to_video(app: &AppHandle, video_path: &str, audio_path: &str, output_path: &str) -> Result<(), String> { + let args = vec![ + "-i".to_string(), video_path.to_string(), + "-i".to_string(), audio_path.to_string(), + "-c:v".to_string(), "copy".to_string(), + "-c:a".to_string(), "aac".to_string(), + "-b:a".to_string(), "192k".to_string(), // 提高码率 + "-ar".to_string(), "44100".to_string(), // 统一采样率 + "-map".to_string(), "0:v:0".to_string(), + "-map".to_string(), "1:a:0".to_string(), + "-shortest".to_string(), + "-y".to_string(), + output_path.to_string() + ]; + run_ffmpeg(app, args).await.map(|_| ()) +} + +/** + * 将封面图转换为一段短视频 (0.5s, 1080x1920, 30fps) + * 带静音音频轨道,避免 concat 时丢失后续片段音频 + */ +pub async fn create_cover_video(app: &AppHandle, input_path: &str, output_path: &str, duration: &str) -> Result<(), String> { + let args = vec![ + "-loop".to_string(), "1".to_string(), + "-i".to_string(), input_path.to_string(), + "-f".to_string(), "lavfi".to_string(), + "-i".to_string(), "anullsrc=r=44100:cl=stereo".to_string(), + "-c:v".to_string(), "libx264".to_string(), + "-c:a".to_string(), "aac".to_string(), + "-t".to_string(), duration.to_string(), + "-pix_fmt".to_string(), "yuv420p".to_string(), + "-vf".to_string(), "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,setsar=1".to_string(), + "-r".to_string(), "30".to_string(), + "-shortest".to_string(), + "-y".to_string(), + output_path.to_string() + ]; + run_ffmpeg(app, args).await.map(|_| ()) +} + +/** + * 获取应用资源目录下的字体路径 + * + * 开发模式下,如果资源目录找不到字体,回退到源码目录 src-tauri/fonts/ + */ +fn get_fonts_dir(app: &AppHandle) -> Result { + use tauri::Manager; + + // 先尝试从资源目录获取(生产模式) + if let Ok(resource_path) = app.path().resource_dir() { + let fonts_path = resource_path.join("fonts"); + println!("[get_fonts_dir] Checking resource path: {:?}", fonts_path); + if fonts_path.exists() { + println!("[get_fonts_dir] Found fonts at: {:?}", fonts_path); + return Ok(fonts_path); + } + } + + // 开发模式回退:从当前工作目录寻找源码中的 fonts 目录 + let cwd = std::env::current_dir() + .map_err(|e| format!("Failed to get cwd: {}", e))?; + + // cwd 通常是 src-tauri 目录,所以直接找 fonts/ + let dev_fonts_path = cwd.join("fonts"); + println!("[get_fonts_dir] Checking dev cwd path: {:?}", dev_fonts_path); + if dev_fonts_path.exists() { + println!("[get_fonts_dir] Found fonts at: {:?}", dev_fonts_path); + return Ok(dev_fonts_path); + } + + // 如果还是找不到,尝试往上一级找 + if let Some(parent) = cwd.parent() { + let dev_fonts_path_alt = parent.join("src-tauri/fonts"); + println!("[get_fonts_dir] Checking dev parent path: {:?}", dev_fonts_path_alt); + if dev_fonts_path_alt.exists() { + println!("[get_fonts_dir] Found fonts at: {:?}", dev_fonts_path_alt); + return Ok(dev_fonts_path_alt); + } + } + + // 尝试绝对路径 + let abs_path = std::path::Path::new("/Users/0fun/work/ai-meijiaka/tauri-app/src-tauri/fonts"); + if abs_path.exists() { + println!("[get_fonts_dir] Found fonts at absolute path: {:?}", abs_path); + return Ok(abs_path.to_path_buf()); + } + + Err("Could not find fonts directory in any location".to_string()) +} + +/** + * 提取视频首帧为图片 + */ +pub async fn extract_first_frame( + app: &AppHandle, + video_path: &str, + output_path: &str, +) -> Result<(), String> { + let args = vec![ + "-i".to_string(), video_path.to_string(), + "-ss".to_string(), "00:00:00".to_string(), + "-vframes".to_string(), "1".to_string(), + "-q:v".to_string(), "2".to_string(), + "-y".to_string(), + output_path.to_string(), + ]; + run_ffmpeg(app, args).await.map(|_| ()) +} + +/** + * 压制 ASS 字幕到视频(使用嵌入字体) + * + * 使用抖音美好体 (DouyinSansBold) 作为默认字体 + */ +pub async fn burn_ass_subtitle( + app: &AppHandle, + video_path: &str, + ass_path: &str, + output_path: &str, +) -> Result<(), String> { + let fonts_dir = get_fonts_dir(app)?; + let fonts_dir_str = fonts_dir.to_str().ok_or("Invalid fonts dir path")?; + + // 转义路径中的特殊字符(FFmpeg 滤镜语法要求) + // 只需要转义冒号 : 因为它是滤镜参数分隔符 + // FFmpeg 中,整个引用路径不需要内部再转义单引号 + let ass_path_escaped = ass_path.replace(":", "\\:"); + let fonts_dir_escaped = fonts_dir_str.replace(":", "\\:"); + + // 使用 ass 滤镜,指定字体目录 + // 语法: ass='path':fontsdir='path' + let filter = format!("ass='{}':fontsdir='{}'", ass_path_escaped, fonts_dir_escaped); + + println!("FFmpeg filter: {}", filter); // 调试日志 + + let args = vec![ + "-i".to_string(), video_path.to_string(), + "-vf".to_string(), filter, + "-c:v".to_string(), "libx264".to_string(), + "-preset".to_string(), "medium".to_string(), + "-crf".to_string(), "23".to_string(), + "-c:a".to_string(), "copy".to_string(), // 音频直接复制 + "-y".to_string(), + output_path.to_string() + ]; + + run_ffmpeg(app, args).await.map(|_| ()) +} + +/** 压制 ASS 字幕到视频(带自定义字体目录) + * 当前未使用,保留为扩展点。 + */ +#[allow(dead_code)] +pub async fn burn_ass_subtitle_with_fonts( + app: &AppHandle, + video_path: &str, + ass_path: &str, + fonts_dir: &str, + output_path: &str, +) -> Result<(), String> { + let filter = format!("ass='{}':fontsdir='{}'", ass_path, fonts_dir); + + let args = vec![ + "-i".to_string(), video_path.to_string(), + "-vf".to_string(), filter, + "-c:v".to_string(), "libx264".to_string(), + "-preset".to_string(), "medium".to_string(), + "-crf".to_string(), "23".to_string(), + "-c:a".to_string(), "copy".to_string(), + "-y".to_string(), + output_path.to_string() + ]; + + run_ffmpeg(app, args).await.map(|_| ()) +} + +/** + * 压制 ASS 字幕到图片(使用嵌入字体) + * + * 用于生成封面图 + */ +pub async fn burn_ass_subtitle_to_image( + app: &AppHandle, + image_path: &str, + ass_path: &str, + output_path: &str, +) -> Result<(), String> { + let fonts_dir = get_fonts_dir(app)?; + let fonts_dir_str = fonts_dir.to_str().ok_or("Invalid fonts dir path")?; + + let ass_path_escaped = ass_path.replace("'", "'\\''").replace(":", "\\:"); + let fonts_dir_escaped = fonts_dir_str.replace("'", "'\\''"); + + let filter = format!("ass='{}':fontsdir='{}'", ass_path_escaped, fonts_dir_escaped); + + let args = vec![ + "-loop".to_string(), "1".to_string(), + "-i".to_string(), image_path.to_string(), + "-vf".to_string(), filter, + "-t".to_string(), "1".to_string(), + "-frames:v".to_string(), "1".to_string(), + "-y".to_string(), + output_path.to_string() + ]; + + run_ffmpeg(app, args).await.map(|_| ()) +} diff --git a/tauri-app/src-tauri/src/lib.rs b/tauri-app/src-tauri/src/lib.rs new file mode 100644 index 0000000..3ce1e39 --- /dev/null +++ b/tauri-app/src-tauri/src/lib.rs @@ -0,0 +1,264 @@ +//! 美家卡智影 Tauri 后端入口 +//! +//! 模块拆分: +//! - ffmpeg_cmd: FFmpeg 命令封装 +//! - persistence: 项目持久化存储 +//! - video_processing: 视频合成业务逻辑 +//! - api_proxy: Python API 代理 +//! - auth: 认证命令 +//! - utils: 通用工具函数 +//! - commands: Tauri IPC 命令入口(按领域拆分) + +mod ffmpeg_cmd; +mod video_processing; +mod api_proxy; +mod auth; +mod utils; +mod avatar_cache; +mod commands; +mod storage; + +use serde::{Deserialize, Serialize}; + +// ============================================================ +// 配置常量 +// ============================================================ + +/// Python API 基础 URL(后端服务地址,与 docker-compose.yml 中 api 服务的对外端口保持一致) +const PYTHON_API_BASE_URL: &str = "http://127.0.0.1:8080/api/v1"; + +// ============================================================ +// 公共类型导出 +// ============================================================ + +/// 前端透传请求结构体(当前未在 Rust 侧读取字段,保留用于未来扩展) +#[allow(dead_code)] +#[derive(Deserialize)] +pub struct ApiRequest { + module: String, + action: String, + payload: serde_json::Value, +} + +#[derive(Serialize)] +pub struct ApiResponse { + pub code: i32, + pub message: String, + pub data: Option, +} + +// ============================================================ +// 应用入口 +// ============================================================ + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + println!("DEBUG: Starting Tauri app with Superpowers..."); + println!("DEBUG: Python API base URL: {}", PYTHON_API_BASE_URL); + + use storage::cache::CacheState; + + tauri::Builder::default() + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_opener::init()) + .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_dialog::init()) + .manage(CacheState::new()) + .invoke_handler(tauri::generate_handler![ + auth::auth_login, + api_proxy::api_call, + // 项目存储 + commands::project::save_project_meta, + commands::project::save_project_meta_raw, + commands::project::load_project_meta, + commands::project::save_project_segments, + commands::project::save_project_segments_raw, + commands::project::load_project_segments, + commands::project::list_local_projects, + commands::project::delete_local_project, + commands::avatar::load_avatars_list, + commands::avatar::save_avatars_list, + commands::auth_state::load_auth_state, + commands::auth_state::save_auth_state, + commands::auth_state::clear_auth_state, + // 头像缓存 + avatar_cache::query_avatar_cache, + avatar_cache::cache_avatar_video, + avatar_cache::save_avatar_poster, + avatar_cache::delete_avatar_cache, + avatar_cache::get_cache_stats, + // 字幕压制 + burn_subtitle, + // 视频首帧提取 + extract_video_first_frame, + // 封面图生成 + generate_cover_image, + // 保存项目资源(封面图片等) + commands::asset::save_project_asset, + // 获取视频保存路径 + commands::asset::get_video_save_path, + // 获取图片保存路径 + commands::asset::get_image_save_path, + // 视频合成 + video_composite_synthesis, + // 保存成品到 products 目录 + commands::product::save_final_product, + // 列出本地成品 + commands::product::list_local_products, + // 删除本地成品 + commands::product::delete_local_product, + // 重命名成品 + commands::product::rename_local_product, + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} + +// ============================================================ +// 字幕压制命令 +// ============================================================ + +#[derive(Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct BurnSubtitleRequest { + video_path: String, + ass_path: String, + output_path: String, +} + +#[tauri::command] +async fn burn_subtitle( + app: tauri::AppHandle, + request: BurnSubtitleRequest, +) -> ApiResponse { + match ffmpeg_cmd::burn_ass_subtitle( + &app, + &request.video_path, + &request.ass_path, + &request.output_path, + ).await { + Ok(_) => ApiResponse { + code: 200, + message: "Subtitle burned successfully".to_string(), + data: Some(request.output_path), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to burn subtitle: {}", e), + data: None, + }, + } +} + +// ============================================================ +// 封面生成命令 +// ============================================================ + +#[derive(Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct ExtractFirstFrameRequest { + video_path: String, + output_path: String, +} + +#[tauri::command] +async fn extract_video_first_frame( + app: tauri::AppHandle, + request: ExtractFirstFrameRequest, +) -> ApiResponse { + match ffmpeg_cmd::extract_first_frame( + &app, + &request.video_path, + &request.output_path, + ).await { + Ok(_) => ApiResponse { + code: 200, + message: "First frame extracted successfully".to_string(), + data: Some(request.output_path), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to extract first frame: {}", e), + data: None, + }, + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct GenerateCoverImageRequest { + image_path: String, + ass_path: String, + output_path: String, +} + +#[tauri::command] +async fn generate_cover_image( + app: tauri::AppHandle, + request: GenerateCoverImageRequest, +) -> ApiResponse { + match ffmpeg_cmd::burn_ass_subtitle_to_image( + &app, + &request.image_path, + &request.ass_path, + &request.output_path, + ).await { + Ok(_) => ApiResponse { + code: 200, + message: "Cover image generated successfully".to_string(), + data: Some(request.output_path), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("Failed to generate cover image: {}", e), + data: None, + }, + } +} + +// ============================================================ +// 视频合成命令 +// ============================================================ + +#[derive(Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct VideoCompositeRequest { + video_paths: Vec, + audio_path: Option, + cover_path: Option, + output_path: String, +} + +#[tauri::command] +async fn video_composite_synthesis( + app: tauri::AppHandle, + request: VideoCompositeRequest, +) -> ApiResponse { + let project_dir = utils::get_project_dir(&app); + let payload = serde_json::json!({ + "videoPaths": request.video_paths, + "audioPath": request.audio_path.unwrap_or_default(), + "coverPath": request.cover_path, + "outputPath": request.output_path, + }); + let response = video_processing::handle_video_synthesis(&app, &project_dir, payload).await; + if response.code == 200 { + if let Some(data) = response.data { + if let Some(path) = data.as_object() + .and_then(|obj| obj.get("outputPath")) + .and_then(|v| v.as_str()) + { + return ApiResponse { + code: 200, + message: "视频合成成功".to_string(), + data: Some(path.to_string()), + }; + } + } + } + ApiResponse { + code: response.code, + message: response.message, + data: None, + } +} diff --git a/tauri-app/src-tauri/src/main.rs b/tauri-app/src-tauri/src/main.rs new file mode 100644 index 0000000..2abccd9 --- /dev/null +++ b/tauri-app/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + tauri_app_lib::run() +} diff --git a/tauri-app/src-tauri/src/storage/auth.rs b/tauri-app/src-tauri/src/storage/auth.rs new file mode 100644 index 0000000..7816f52 --- /dev/null +++ b/tauri-app/src-tauri/src/storage/auth.rs @@ -0,0 +1,45 @@ +//! 认证状态存储 +//! +//! 负责 auth.json 的读写,使用原子写入 + 文件锁 + Unix 0600 权限。 + +use serde::Serialize; +use serde::de::DeserializeOwned; +use tauri::AppHandle; +use crate::storage::engine::{ + atomic_write_json, with_file_lock, read_json, + restrict_file_permissions, StorageError, +}; +use crate::storage::paths::get_auth_state_path; + +/// 保存认证状态 +pub fn save_auth_state(app: &AppHandle, state: &T) -> Result<(), StorageError> +where + T: Serialize, +{ + let path = get_auth_state_path(app)?; + with_file_lock(&path, || { + atomic_write_json(&path, state)?; + restrict_file_permissions(&path)?; + Ok(()) + }) +} + +/// 加载认证状态 +pub fn load_auth_state(app: &AppHandle) -> Result, StorageError> +where + T: DeserializeOwned, +{ + let path = get_auth_state_path(app)?; + read_json(&path) +} + +/// 清除认证状态 +pub fn clear_auth_state(app: &AppHandle) -> Result<(), StorageError> { + let path = get_auth_state_path(app)?; + // 直接尝试删除,忽略 NotFound 错误 + match std::fs::remove_file(&path) { + Ok(()) => Ok(()), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()), + Err(e) => Err(e.into()), + } +} diff --git a/tauri-app/src-tauri/src/storage/avatar.rs b/tauri-app/src-tauri/src/storage/avatar.rs new file mode 100644 index 0000000..1e7875e --- /dev/null +++ b/tauri-app/src-tauri/src/storage/avatar.rs @@ -0,0 +1,26 @@ +//! 克隆形象列表存储 +//! +//! 负责 avatars.json 的读写,使用原子写入 + 文件锁。 + +use serde_json::Value; +use crate::storage::engine::{ + atomic_write_json, with_file_lock, read_json, StorageError, +}; +use crate::storage::paths::get_avatars_json_path; + +/// 保存头像列表 +pub fn save_avatars_list(avatars: &Vec) -> Result<(), StorageError> { + let path = get_avatars_json_path()?; + with_file_lock(&path, || { + atomic_write_json(&path, avatars) + }) +} + +/// 加载头像列表 +pub fn load_avatars_list() -> Result, StorageError> { + let path = get_avatars_json_path()?; + match read_json(&path)? { + Some(data) => Ok(data), + None => Ok(vec![]), + } +} diff --git a/tauri-app/src-tauri/src/storage/cache.rs b/tauri-app/src-tauri/src/storage/cache.rs new file mode 100644 index 0000000..ad51b49 --- /dev/null +++ b/tauri-app/src-tauri/src/storage/cache.rs @@ -0,0 +1,451 @@ +//! 头像缓存存储层 +//! +//! 负责缓存索引(index.json)和视频/封面文件的读写。 +//! 索引操作受 Mutex 保护,文件写入使用原子写入。 + +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; +use std::sync::Mutex; +use serde::{Deserialize, Serialize}; +use tauri::AppHandle; +use crate::storage::engine::{ + atomic_write_json, atomic_write_bytes, read_json, + sanitize_id, StorageError, +}; +use crate::storage::paths::{ + get_cache_root, get_cache_videos_dir, get_cache_posters_dir, +}; + +// ============================================================ +// 常量 +// ============================================================ + +/// 最大缓存大小:500MB +const MAX_CACHE_SIZE_BYTES: u64 = 500 * 1024 * 1024; +/// 清理阈值:达到上限的 80% 时开始清理 +const CACHE_CLEAN_THRESHOLD: f64 = 0.8; + +// ============================================================ +// 数据结构 +// ============================================================ + +/// 缓存索引状态(受 Mutex 保护) +pub struct CacheState { + pub index: Mutex, + pub loaded: std::sync::atomic::AtomicBool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct AvatarCacheIndex { + pub version: u32, + pub cached_avatars: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AvatarCacheEntry { + pub avatar_id: String, + pub remote_url: String, + pub video_path: String, // 相对路径 + pub poster_path: Option, // 相对路径 + pub cached_at: String, // ISO 8601 + pub file_size: u64, // 字节 +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CacheQueryResult { + pub cached: bool, + pub local_video_path: Option, + pub local_poster_path: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CacheSaveResult { + pub success: bool, + pub local_path: Option, + pub message: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CacheDeleteResult { + pub success: bool, + pub message: Option, +} + +// ============================================================ +// 索引操作 +// ============================================================ + +impl CacheState { + pub fn new() -> Self { + Self { + index: Mutex::new(AvatarCacheIndex::default()), + loaded: std::sync::atomic::AtomicBool::new(false), + } + } + + /// 确保索引已从磁盘加载(幂等) + pub fn ensure_loaded(&self, app: &AppHandle) -> Result<(), StorageError> { + if self.loaded.load(std::sync::atomic::Ordering::SeqCst) { + return Ok(()); + } + self.load_from_disk(app)?; + self.loaded.store(true, std::sync::atomic::Ordering::SeqCst); + Ok(()) + } + + /// 从磁盘加载索引到 Mutex(内部使用) + fn load_from_disk(&self, app: &AppHandle) -> Result<(), StorageError> { + let root = get_cache_root(app)?; + let index_path = root.join("index.json"); + let index = match read_json::(&index_path)? { + Some(idx) => idx, + None => AvatarCacheIndex::default(), + }; + let mut guard = self.index.lock().map_err(|_| { + StorageError::LockFailed("index mutex poisoned".into()) + })?; + *guard = index; + Ok(()) + } + + /// 保存索引到磁盘 + pub fn save_to_disk(&self, app: &AppHandle) -> Result<(), StorageError> { + let root = get_cache_root(app)?; + let index_path = root.join("index.json"); + let index = self.index.lock().map_err(|_| { + StorageError::LockFailed("index mutex poisoned".into()) + })?; + atomic_write_json(&index_path, &*index) + } +} + +// ============================================================ +// 查询 +// ============================================================ + +/// 查询指定 avatar 的缓存状态 +pub fn query_avatar_cache( + app: &AppHandle, + state: &CacheState, + avatar_id: &str, +) -> Result { + state.ensure_loaded(app)?; + let safe_id = sanitize_id(avatar_id)?; + let root = get_cache_root(app)?; + + let mut index = state.index.lock().map_err(|_| { + StorageError::LockFailed("index mutex poisoned".into()) + })?; + + let entry = match index.cached_avatars.get(&safe_id) { + Some(e) => e, + None => { + return Ok(CacheQueryResult { + cached: false, + local_video_path: None, + local_poster_path: None, + }); + } + }; + + let video_path = root.join(&entry.video_path); + if !video_path.exists() { + // 文件不存在,清理索引(不保存,由调用者决定何时保存) + index.cached_avatars.remove(&safe_id); + return Ok(CacheQueryResult { + cached: false, + local_video_path: None, + local_poster_path: None, + }); + } + + let local_video_path = Some(video_path.to_string_lossy().to_string()); + let local_poster_path = entry.poster_path.as_ref().map(|p| { + root.join(p).to_string_lossy().to_string() + }); + + Ok(CacheQueryResult { + cached: true, + local_video_path, + local_poster_path, + }) +} + +// ============================================================ +// 保存视频 +// ============================================================ + +/// 保存视频到缓存 +/// +/// 调用者需先下载好字节数据,此函数负责写入文件和更新索引。 +pub fn save_cached_video( + app: &AppHandle, + state: &CacheState, + avatar_id: &str, + remote_url: &str, + data: &[u8], +) -> Result { + let safe_id = sanitize_id(avatar_id)?; + let root = get_cache_root(app)?; + let videos_dir = get_cache_videos_dir(app)?; + + let video_relative = format!("videos/{}.mp4", safe_id); + let video_absolute = videos_dir.join(format!("{}.mp4", safe_id)); + + // 1. 清理(如果需要) + { + let mut index = state.index.lock().map_err(|_| { + StorageError::LockFailed("index mutex poisoned".into()) + })?; + cleanup_if_needed(app, &root, &mut index)?; + } + + // 2. 写入文件(原子写入) + atomic_write_bytes(&video_absolute, data)?; + let file_size = data.len() as u64; + + // 3. 更新索引 + { + let mut index = state.index.lock().map_err(|_| { + StorageError::LockFailed("index mutex poisoned".into()) + })?; + + let entry = AvatarCacheEntry { + avatar_id: safe_id.clone(), + remote_url: remote_url.to_string(), + video_path: video_relative, + poster_path: None, + cached_at: chrono::Utc::now().to_rfc3339(), + file_size, + }; + index.cached_avatars.insert(safe_id, entry); + } + + // 4. 保存索引 + state.save_to_disk(app)?; + + Ok(CacheSaveResult { + success: true, + local_path: Some(video_absolute.to_string_lossy().to_string()), + message: None, + }) +} + +// ============================================================ +// 保存封面 +// ============================================================ + +/// 保存封面到缓存 +pub fn save_cached_poster( + app: &AppHandle, + state: &CacheState, + avatar_id: &str, + data: &[u8], +) -> Result { + state.ensure_loaded(app)?; + let safe_id = sanitize_id(avatar_id)?; + let posters_dir = get_cache_posters_dir(app)?; + let poster_relative = format!("posters/{}.jpg", safe_id); + let poster_absolute = posters_dir.join(format!("{}.jpg", safe_id)); + + // 写入文件 + atomic_write_bytes(&poster_absolute, data)?; + + // 更新索引 + { + let mut index = state.index.lock().map_err(|_| { + StorageError::LockFailed("index mutex poisoned".into()) + })?; + + if let Some(entry) = index.cached_avatars.get_mut(&safe_id) { + entry.poster_path = Some(poster_relative); + } + // 如果 avatar 不在索引中,不记录 poster(避免孤儿文件无索引记录) + } + + state.save_to_disk(app)?; + + Ok(CacheSaveResult { + success: true, + local_path: Some(poster_absolute.to_string_lossy().to_string()), + message: None, + }) +} + +// ============================================================ +// 删除 +// ============================================================ + +/// 删除指定 avatar 的缓存 +pub fn delete_avatar_cache( + app: &AppHandle, + state: &CacheState, + avatar_id: &str, +) -> Result { + state.ensure_loaded(app)?; + let safe_id = sanitize_id(avatar_id)?; + let root = get_cache_root(app)?; + + let mut index = state.index.lock().map_err(|_| { + StorageError::LockFailed("index mutex poisoned".into()) + })?; + + let entry = match index.cached_avatars.remove(&safe_id) { + Some(e) => e, + None => { + return Ok(CacheDeleteResult { + success: true, + message: Some("Entry not found in cache index".to_string()), + }); + } + }; + + // 删除视频文件 + let video_path = root.join(&entry.video_path); + if video_path.exists() { + if let Err(e) = fs::remove_file(&video_path) { + eprintln!("[cache] Failed to remove video file: {}", e); + } + } + + // 删除封面文件 + if let Some(poster_path) = &entry.poster_path { + let poster_abs = root.join(poster_path); + if poster_abs.exists() { + if let Err(e) = fs::remove_file(&poster_abs) { + eprintln!("[cache] Failed to remove poster file: {}", e); + } + } + } + + // 保存索引 + drop(index); // 先释放锁,save_to_disk 会重新获取 + state.save_to_disk(app)?; + + Ok(CacheDeleteResult { + success: true, + message: None, + }) +} + +// ============================================================ +// 统计 +// ============================================================ + +/// 获取缓存统计信息 +pub fn get_cache_stats( + app: &AppHandle, + state: &CacheState, +) -> Result { + state.ensure_loaded(app)?; + let index = state.index.lock().map_err(|_| { + StorageError::LockFailed("index mutex poisoned".into()) + })?; + + let total_count = index.cached_avatars.len(); + let total_size: u64 = calculate_actual_cache_size(app, &index)?; + let usage_percent = (total_size as f64 / MAX_CACHE_SIZE_BYTES as f64 * 100.0).round(); + + Ok(serde_json::json!({ + "code": 200, + "data": { + "count": total_count, + "total_size_bytes": total_size, + "total_size_mb": (total_size as f64 / (1024.0 * 1024.0)).round(), + "max_size_mb": MAX_CACHE_SIZE_BYTES / (1024 * 1024), + "usage_percent": usage_percent, + "threshold_percent": (CACHE_CLEAN_THRESHOLD * 100.0) as u32 + } + })) +} + +// ============================================================ +// 内部辅助 +// ============================================================ + +/// 基于实际文件大小计算缓存总大小 +fn calculate_actual_cache_size( + app: &AppHandle, + index: &AvatarCacheIndex, +) -> Result { + let root = get_cache_root(app)?; + let mut total = 0u64; + for entry in index.cached_avatars.values() { + let video_path = root.join(&entry.video_path); + if video_path.exists() { + if let Ok(meta) = fs::metadata(&video_path) { + total += meta.len(); + } + } + if let Some(poster_path) = &entry.poster_path { + let poster_abs = root.join(poster_path); + if poster_abs.exists() { + if let Ok(meta) = fs::metadata(&poster_abs) { + total += meta.len(); + } + } + } + } + Ok(total) +} + +/// 如果缓存超过阈值,清理最旧的条目 +fn cleanup_if_needed( + app: &AppHandle, + root: &PathBuf, + index: &mut AvatarCacheIndex, +) -> Result<(), StorageError> { + let actual_size = calculate_actual_cache_size(app, index)?; + let target_size = (MAX_CACHE_SIZE_BYTES as f64 * CACHE_CLEAN_THRESHOLD) as u64; + + if actual_size <= target_size { + return Ok(()); + } + + let mut current_size = actual_size; + let mut to_remove = Vec::new(); + + // 按缓存时间排序(最旧的在前) + let mut entries: Vec<_> = index.cached_avatars.iter().collect(); + entries.sort_by(|a, b| a.1.cached_at.cmp(&b.1.cached_at)); + + for (avatar_id, entry) in entries { + if current_size <= target_size { + break; + } + + // 删除视频 + let video_path = root.join(&entry.video_path); + if video_path.exists() { + if let Ok(meta) = fs::metadata(&video_path) { + let _ = fs::remove_file(&video_path); + current_size -= meta.len(); + } + } + + // 删除封面 + if let Some(poster_path) = &entry.poster_path { + let poster_abs = root.join(poster_path); + if poster_abs.exists() { + if let Ok(meta) = fs::metadata(&poster_abs) { + let _ = fs::remove_file(&poster_abs); + current_size -= meta.len(); + } + } + } + + to_remove.push(avatar_id.clone()); + } + + for avatar_id in to_remove { + index.cached_avatars.remove(&avatar_id); + } + + println!( + "[cache] Cleanup complete, current size: {} MB", + current_size / (1024 * 1024) + ); + + Ok(()) +} diff --git a/tauri-app/src-tauri/src/storage/engine.rs b/tauri-app/src-tauri/src/storage/engine.rs new file mode 100644 index 0000000..8964eac --- /dev/null +++ b/tauri-app/src-tauri/src/storage/engine.rs @@ -0,0 +1,169 @@ +//! Storage Engine 核心 +//! +//! 所有本地文件操作的基础能力: +//! - sanitize_*: 路径净化,防御路径遍历 +//! - atomic_write_*: 原子写入,防崩溃截断 +//! - with_file_lock: 文件锁,防并发竞态 +//! - read_json: 安全读取,文件不存在返回 None + +use std::fs; +use std::path::Path; +use serde::{de::DeserializeOwned, Serialize}; +use thiserror::Error; + +/// 存储层统一错误类型 +#[derive(Debug, Error)] +pub enum StorageError { + #[error("Invalid identifier: {0}")] + InvalidId(String), + #[error("Path traversal detected")] + PathTraversal, + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + #[error("Serialization error: {0}")] + Serialization(#[from] serde_json::Error), + #[error("Lock acquisition failed: {0}")] + LockFailed(String), +} + +/// 将 StorageError 转换为 String +impl From for String { + fn from(err: StorageError) -> Self { + err.to_string() + } +} + +/// ID 白名单校验 +/// +/// 只允许字母、数字、下划线、连字符。 +/// 用于 project_id, avatar_id 等标识符。 +pub fn sanitize_id(id: &str) -> Result { + if id.is_empty() || id.len() > 64 { + return Err(StorageError::InvalidId( + "empty or too long".into(), + )); + } + if !id.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') { + return Err(StorageError::InvalidId( + "contains illegal characters".into(), + )); + } + Ok(id.to_string()) +} + +/// 文件名净化 +/// +/// 提取纯文件名部分,拒绝任何目录组件(如 `../../etc/passwd`)。 +/// 返回净化后的文件名字符串。 +pub fn sanitize_filename(name: &str) -> Result { + let path = Path::new(name); + let file_name = path + .file_name() + .and_then(|n| n.to_str()) + .ok_or(StorageError::PathTraversal)?; + if file_name.is_empty() || file_name.len() > 255 { + return Err(StorageError::InvalidId( + "invalid filename".into(), + )); + } + Ok(file_name.to_string()) +} + +/// 原子写入 JSON 文件 +/// +/// 先写入 `.tmp` 临时文件,再通过 `fs::rename` 原子替换目标文件。 +/// 确保崩溃或断电时不会留下半写文件。 +pub fn atomic_write_json( + path: &Path, + value: &impl Serialize, +) -> Result<(), StorageError> { + let tmp = path.with_extension("tmp"); + let content = serde_json::to_string_pretty(value)?; + fs::write(&tmp, content)?; + fs::rename(&tmp, path)?; + Ok(()) +} + +/// 原子写入二进制文件 +/// +/// 同 `atomic_write_json`,但写入原始字节。 +pub fn atomic_write_bytes( + path: &Path, + data: &[u8], +) -> Result<(), StorageError> { + let tmp = path.with_extension("tmp"); + fs::write(&tmp, data)?; + fs::rename(&tmp, path)?; + Ok(()) +} + +/// 在文件锁保护下执行操作 +/// +/// 对目标文件创建 `.lock` 锁文件,加独占锁后执行闭包, +/// 完成后自动释放锁并清理锁文件。 +/// +/// 注意:锁粒度为单个文件,不同文件之间互不阻塞。 +pub fn with_file_lock( + path: &Path, + f: impl FnOnce() -> Result, +) -> Result { + let lock_path = path.with_extension("lock"); + let file = fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(&lock_path) + .map_err(|e| StorageError::LockFailed(e.to_string()))?; + + fs2::FileExt::lock_exclusive(&file) + .map_err(|e| StorageError::LockFailed(e.to_string()))?; + + let result = f(); + + let _ = fs2::FileExt::unlock(&file); + let _ = fs::remove_file(&lock_path); + + result +} + +/// 安全读取 JSON 文件 +/// +/// - 文件不存在 → 返回 `Ok(None)` +/// - 解析失败 → 返回 `Err(StorageError::Serialization)` +/// - 成功 → 返回 `Ok(Some(T))` +pub fn read_json( + path: &Path, +) -> Result, StorageError> { + match fs::read_to_string(path) { + Ok(content) => { + let value = serde_json::from_str(&content)?; + Ok(Some(value)) + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), + Err(e) => Err(e.into()), + } +} + +/// 确保目录存在 +/// +/// 幂等操作,多线程并发调用安全。 +pub fn ensure_dir(path: &Path) -> Result<(), StorageError> { + if !path.exists() { + fs::create_dir_all(path)?; + } + Ok(()) +} + +/// 设置 Unix 文件权限为 0600(仅所有者可读写) +/// +/// 非 Unix 平台上为无操作。 +pub fn restrict_file_permissions(path: &Path) -> Result<(), StorageError> { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = fs::metadata(path)?.permissions(); + perms.set_mode(0o600); + fs::set_permissions(path, perms)?; + } + Ok(()) +} diff --git a/tauri-app/src-tauri/src/storage/mod.rs b/tauri-app/src-tauri/src/storage/mod.rs new file mode 100644 index 0000000..a02e7ee --- /dev/null +++ b/tauri-app/src-tauri/src/storage/mod.rs @@ -0,0 +1,22 @@ +//! Storage Engine — 本地文件存储统一抽象层 +//! +//! 提供路径净化、原子写入、文件锁三大核心能力, +//! 所有本地 JSON/二进制文件的读写必须通过此层。 + +mod engine; +mod paths; + +pub mod project; +pub mod avatar; +pub mod auth; +pub mod cache; + +pub use engine::{ + atomic_write_json, atomic_write_bytes, + with_file_lock, read_json, StorageError, +}; + +pub use paths::{ + get_project_dir, get_project_assets_dir, + get_products_dir, get_cache_root, +}; diff --git a/tauri-app/src-tauri/src/storage/paths.rs b/tauri-app/src-tauri/src/storage/paths.rs new file mode 100644 index 0000000..4357ea9 --- /dev/null +++ b/tauri-app/src-tauri/src/storage/paths.rs @@ -0,0 +1,121 @@ +//! 路径计算集中管理 +//! +//! 所有本地存储路径的计算逻辑统一放在此处,禁止在业务代码中硬编码路径。 +//! 所有返回 PathBuf 的函数内部已做路径净化。 + +use std::path::PathBuf; +use tauri::{AppHandle, Manager}; +use crate::storage::engine::{sanitize_id, StorageError}; + +/// 获取美家卡用户文档目录 +/// ~/Documents/Meijiaka/ +pub fn get_meijiaka_dir() -> Result { + let base = dirs::document_dir() + .ok_or_else(|| StorageError::Io(std::io::Error::new( + std::io::ErrorKind::NotFound, + "无法获取文档目录", + )))?; + Ok(base.join("Meijiaka")) +} + +/// 获取项目目录路径(不自动创建) +/// ~/Documents/Meijiaka/projects/{project_id}/ +pub fn get_project_dir_path(project_id: &str) -> Result { + let safe_id = sanitize_id(project_id)?; + let base = get_meijiaka_dir()?; + Ok(base.join("projects").join(&safe_id)) +} + +/// 获取项目目录(自动创建) +/// ~/Documents/Meijiaka/projects/{project_id}/ +pub fn get_project_dir(project_id: &str) -> Result { + let path = get_project_dir_path(project_id)?; + crate::storage::engine::ensure_dir(&path)?; + Ok(path) +} + +/// 获取项目内的 assets 目录 +/// ~/Documents/Meijiaka/projects/{project_id}/assets/ +pub fn get_project_assets_dir(project_id: &str) -> Result { + let path = get_project_dir(project_id)?; + let assets = path.join("assets"); + crate::storage::engine::ensure_dir(&assets)?; + Ok(assets) +} + +/// 获取项目内的 videos 目录 +/// ~/Documents/Meijiaka/projects/{project_id}/videos/ +pub fn get_project_videos_dir(project_id: &str) -> Result { + let path = get_project_dir(project_id)?; + let videos = path.join("videos"); + crate::storage::engine::ensure_dir(&videos)?; + Ok(videos) +} + +/// 获取项目内的 images 目录 +/// ~/Documents/Meijiaka/projects/{project_id}/images/ +pub fn get_project_images_dir(project_id: &str) -> Result { + let path = get_project_dir(project_id)?; + let images = path.join("images"); + crate::storage::engine::ensure_dir(&images)?; + Ok(images) +} + +/// 获取 products 目录 +/// ~/Documents/Meijiaka/products/ +pub fn get_products_dir() -> Result { + let base = get_meijiaka_dir()?; + let path = base.join("products"); + crate::storage::engine::ensure_dir(&path)?; + Ok(path) +} + +/// 获取头像列表 JSON 路径 +/// ~/Documents/Meijiaka/avatars.json +pub fn get_avatars_json_path() -> Result { + let base = get_meijiaka_dir()?; + Ok(base.join("avatars.json")) +} + +/// 获取认证状态文件路径 +/// {app_config_dir}/auth.json +pub fn get_auth_state_path(app: &AppHandle) -> Result { + let path = app.path().app_config_dir() + .map_err(|e| StorageError::Io(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("获取应用配置目录失败: {}", e), + )))?; + crate::storage::engine::ensure_dir(&path)?; + Ok(path.join("auth.json")) +} + +/// 获取头像缓存根目录 +/// {app_data_dir}/avatars/ +pub fn get_cache_root(app: &AppHandle) -> Result { + let app_data = app.path().app_data_dir() + .map_err(|e| StorageError::Io(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("获取应用数据目录失败: {}", e), + )))?; + let root = app_data.join("avatars"); + crate::storage::engine::ensure_dir(&root)?; + Ok(root) +} + +/// 获取头像缓存视频目录 +/// {app_data_dir}/avatars/videos/ +pub fn get_cache_videos_dir(app: &AppHandle) -> Result { + let root = get_cache_root(app)?; + let dir = root.join("videos"); + crate::storage::engine::ensure_dir(&dir)?; + Ok(dir) +} + +/// 获取头像缓存封面目录 +/// {app_data_dir}/avatars/posters/ +pub fn get_cache_posters_dir(app: &AppHandle) -> Result { + let root = get_cache_root(app)?; + let dir = root.join("posters"); + crate::storage::engine::ensure_dir(&dir)?; + Ok(dir) +} diff --git a/tauri-app/src-tauri/src/storage/project.rs b/tauri-app/src-tauri/src/storage/project.rs new file mode 100644 index 0000000..582f734 --- /dev/null +++ b/tauri-app/src-tauri/src/storage/project.rs @@ -0,0 +1,250 @@ +//! 项目数据存储 +//! +//! 负责项目元数据(meta.json)和分镜数据(segments.json)的读写。 +//! 所有操作通过 StorageEngine 的路径净化 + 原子写入 + 文件锁。 + +use std::fs; +use std::path::PathBuf; +use serde_json::Value; +use base64::Engine as _; +use crate::storage::engine::{ + atomic_write_json, atomic_write_bytes, with_file_lock, read_json, + sanitize_filename, StorageError, +}; +use crate::storage::paths::{ + get_project_dir, get_project_dir_path, get_project_assets_dir, get_project_videos_dir, get_project_images_dir, get_products_dir, +}; + +// ============================================================ +// 元数据读写 +// ============================================================ + +/// 保存项目元数据 +pub fn save_project_meta( + project_id: &str, + data: &Value, +) -> Result<(), StorageError> { + let dir = get_project_dir(project_id)?; + let path = dir.join("meta.json"); + with_file_lock(&path, || { + atomic_write_json(&path, data) + }) +} + +/// 保存项目元数据(原始 JSON 字符串) +pub fn save_project_meta_raw( + project_id: &str, + json_content: &str, +) -> Result<(), StorageError> { + let data: Value = serde_json::from_str(json_content)?; + save_project_meta(project_id, &data) +} + +/// 加载项目元数据 +pub fn load_project_meta( + project_id: &str, +) -> Result { + let dir = get_project_dir_path(project_id)?; + let path = dir.join("meta.json"); + match read_json(&path)? { + Some(data) => Ok(data), + None => Ok(Value::Null), + } +} + +// ============================================================ +// 分镜数据读写 +// ============================================================ + +/// 保存分镜数据 +pub fn save_project_segments( + project_id: &str, + segments: &Value, +) -> Result<(), StorageError> { + let dir = get_project_dir(project_id)?; + let path = dir.join("segments.json"); + with_file_lock(&path, || { + atomic_write_json(&path, segments) + }) +} + +/// 保存分镜数据(原始 JSON 字符串) +pub fn save_project_segments_raw( + project_id: &str, + json_content: &str, +) -> Result<(), StorageError> { + let data: Value = serde_json::from_str(json_content)?; + save_project_segments(project_id, &data) +} + +/// 加载分镜数据 +pub fn load_project_segments( + project_id: &str, +) -> Result { + let dir = get_project_dir_path(project_id)?; + let path = dir.join("segments.json"); + match read_json(&path)? { + Some(data) => Ok(data), + None => Ok(Value::Array(vec![])), + } +} + +// ============================================================ +// 项目列表 +// ============================================================ + +/// 列出所有本地项目 +/// +/// 遍历 projects 目录,读取每个项目的 meta.json。 +/// 读取失败的项目会被跳过并记录警告,不会导致整个列表失败。 +pub fn list_projects() -> Result, StorageError> { + let base = get_meijiaka_dir()?; + let projects_dir = base.join("projects"); + + if !projects_dir.exists() { + return Ok(vec![]); + } + + let mut projects = Vec::new(); + for entry in fs::read_dir(&projects_dir)? { + let entry = match entry { + Ok(e) => e, + Err(e) => { + eprintln!("[storage] Failed to read directory entry: {}", e); + continue; + } + }; + + let path = entry.path(); + if !path.is_dir() { + continue; + } + + let meta_path = path.join("meta.json"); + match read_json::(&meta_path) { + Ok(Some(meta)) => projects.push(meta), + Ok(None) => { + eprintln!("[storage] meta.json not found for project: {:?}", path); + } + Err(e) => { + eprintln!("[storage] Failed to parse meta.json for {:?}: {}", path, e); + } + } + } + + Ok(projects) +} + +// ============================================================ +// 删除项目 +// ============================================================ + +/// 删除本地项目 +/// +/// 使用 `get_project_dir_path`(不自动创建目录), +/// 避免删除不存在项目时先创建空目录的 bug。 +pub fn delete_project(project_id: &str) -> Result<(), StorageError> { + let dir = get_project_dir_path(project_id)?; + if dir.exists() { + fs::remove_dir_all(&dir)?; + } + Ok(()) +} + +// ============================================================ +// 资源文件 +// ============================================================ + +/// 保存项目资源(Base64 解码后写入) +pub fn save_project_asset( + project_id: &str, + filename: &str, + base64_data: &str, +) -> Result { + let safe_filename = sanitize_filename(filename)?; + let dir = get_project_assets_dir(project_id)?; + let path = dir.join(&safe_filename); + + let decoded = base64::engine::general_purpose::STANDARD + .decode(base64_data) + .map_err(|e| StorageError::InvalidId(format!("base64 decode failed: {}", e)))?; + + atomic_write_bytes(&path, &decoded)?; + + Ok(path.to_string_lossy().to_string()) +} + +/// 获取视频保存路径(不写入文件) +/// 保存到项目 videos 目录下,与后端下载路径保持一致 +pub fn get_video_save_path( + project_id: &str, + filename: &str, +) -> Result { + let safe_filename = sanitize_filename(filename)?; + let dir = get_project_videos_dir(project_id)?; + Ok(dir.join(&safe_filename)) +} + +/// 获取图片保存路径(不写入文件) +/// 保存到项目 images 目录下 +pub fn get_image_save_path( + project_id: &str, + filename: &str, +) -> Result { + let safe_filename = sanitize_filename(filename)?; + let dir = get_project_images_dir(project_id)?; + Ok(dir.join(&safe_filename)) +} + +/// 获取封面保存路径 +pub fn get_cover_save_path( + project_id: &str, + filename: &str, +) -> Result { + let safe_filename = sanitize_filename(filename)?; + let dir = get_project_assets_dir(project_id)?; + Ok(dir.join(&safe_filename)) +} + +// ============================================================ +// 成品保存 +// ============================================================ + +/// 保存成品到 products 目录 +/// +/// 将源文件复制到 products 目录,并更新项目元数据中的 finalVideoPath。 +pub fn save_final_product_to_products( + project_id: &str, + source_path: &str, + product_filename: &str, +) -> Result { + let safe_filename = sanitize_filename(product_filename)?; + let products_dir = get_products_dir()?; + + // 目标路径 + let target_path = products_dir.join(&safe_filename); + fs::copy(source_path, &target_path)?; + + // 更新项目元数据 + let dir = get_project_dir(project_id)?; + let meta_path = dir.join("meta.json"); + + with_file_lock(&meta_path, || { + let mut meta = load_project_meta(project_id)?; + if meta.is_null() { + meta = serde_json::json!({}); + } + if let Some(obj) = meta.as_object_mut() { + obj.insert("finalVideoPath".to_string(), serde_json::json!(target_path.to_string_lossy().to_string())); + obj.insert("exportedAt".to_string(), serde_json::json!(chrono::Utc::now().timestamp_millis())); + } + atomic_write_json(&meta_path, &meta) + })?; + + Ok(target_path.to_string_lossy().to_string()) +} + +// 兼容旧接口:从 persistence.rs 迁移时需要的 get_meijiaka_dir +fn get_meijiaka_dir() -> Result { + crate::storage::paths::get_meijiaka_dir() +} diff --git a/tauri-app/src-tauri/src/utils.rs b/tauri-app/src-tauri/src/utils.rs new file mode 100644 index 0000000..4c09b0b --- /dev/null +++ b/tauri-app/src-tauri/src/utils.rs @@ -0,0 +1,21 @@ +//! 通用工具函数 + +use uuid::Uuid; +use tauri::Manager; + +/// 生成 UUID v4 字符串 +pub fn uuid_v4() -> String { + Uuid::new_v4().to_string() +} + +/// 获取项目存储目录 +/// 如果目录不存在则创建 +pub fn get_project_dir(app: &tauri::AppHandle) -> std::path::PathBuf { + let mut path = app.path().document_dir().unwrap_or_else(|_| std::env::temp_dir()); + path.push("Meijiaka"); + path.push("Projects"); + if !path.exists() { + let _ = std::fs::create_dir_all(&path); + } + path +} diff --git a/tauri-app/src-tauri/src/video_processing.rs b/tauri-app/src-tauri/src/video_processing.rs new file mode 100644 index 0000000..6c26ab5 --- /dev/null +++ b/tauri-app/src-tauri/src/video_processing.rs @@ -0,0 +1,176 @@ +//! 视频合成处理 + +use serde_json; +use crate::utils; +use crate::ApiResponse; + +/// 处理视频合成请求 +pub async fn handle_video_synthesis( + app: &tauri::AppHandle, + project_dir: &std::path::PathBuf, + payload: serde_json::Value, +) -> ApiResponse { + let payload_obj = match payload.as_object() { + Some(p) => p, + None => return ApiResponse { + code: 400, + message: "Invalid payload: expected object".to_string(), + data: None, + } + }; + + let video_paths = payload_obj.get("videoPaths").and_then(|v| v.as_array()); + let audio_path = payload_obj.get("audioPath").and_then(|v| v.as_str()); + let cover_path = payload_obj.get("coverPath").and_then(|v| v.as_str()); + let output_path = payload_obj.get("outputPath").and_then(|v| v.as_str()); + + let output_path = match output_path { + Some(p) => p, + None => return ApiResponse { + code: 400, + message: "Missing required field: outputPath".to_string(), + data: None, + } + }; + + if let Some(v_paths) = video_paths { + let mut concat_output = project_dir.clone(); + concat_output.push(format!("temp_concat_{}.mp4", utils::uuid_v4())); + let mut final_output = project_dir.clone(); + final_output.push(format!("final_out_{}.mp4", utils::uuid_v4())); + + let mut paths_vec: Vec = v_paths.iter() + .filter_map(|v| v.as_str().map(|s| s.to_string())) + .collect(); + + if paths_vec.is_empty() { + return ApiResponse { + code: 400, + message: "No valid video paths provided".to_string(), + data: None + }; + } + + let mut temp_cover_video: Option = None; + + // 如果有封面,先转换为视频 + if let Some(c_path) = cover_path { + let mut cover_v_path = project_dir.clone(); + cover_v_path.push(format!("temp_cover_{}.mp4", utils::uuid_v4())); + let cover_v_path_str = cover_v_path.to_string_lossy().to_string(); + + match crate::ffmpeg_cmd::create_cover_video(app, c_path, &cover_v_path_str, "0.5").await { + Ok(_) => { + paths_vec.insert(0, cover_v_path_str); + temp_cover_video = Some(cover_v_path); + } + Err(e) => { + return ApiResponse { + code: 500, + message: format!("封面视频转换失败: {}", e), + data: None, + }; + } + } + } + + // 执行视频拼接 + match crate::ffmpeg_cmd::concat_videos_robust(app, paths_vec, concat_output.to_string_lossy().as_ref()).await { + Ok(_) => { + // 如果有音频,合并音频;否则直接使用拼接结果 + let has_audio = audio_path.map(|p| !p.is_empty()).unwrap_or(false); + + if has_audio { + // 合并音频 + match crate::ffmpeg_cmd::add_audio_to_video( + app, + concat_output.to_string_lossy().as_ref(), + audio_path.unwrap(), + final_output.to_string_lossy().as_ref(), + ).await { + Ok(_) => { + // 移动到目标路径 + let move_result = std::fs::rename(&final_output, output_path) + .or_else(|_| { + std::fs::copy(&final_output, output_path).map(|_| { + let _ = std::fs::remove_file(&final_output); + }) + }); + // 清理临时文件 + let _ = std::fs::remove_file(&concat_output); + if let Some(ref p) = temp_cover_video { + let _ = std::fs::remove_file(p); + } + match move_result { + Ok(_) => ApiResponse { + code: 200, + message: "视频合成成功".to_string(), + data: Some(serde_json::json!({ + "outputPath": output_path, + })), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("移动最终视频失败: {}", e), + data: None, + } + } + } + Err(e) => { + let _ = std::fs::remove_file(&concat_output); + if let Some(ref p) = temp_cover_video { + let _ = std::fs::remove_file(p); + } + ApiResponse { + code: 500, + message: format!("音频合并失败: {}", e), + data: None + } + } + } + } else { + // 无音频,移动到目标路径 + let move_result = std::fs::rename(&concat_output, output_path) + .or_else(|_| { + std::fs::copy(&concat_output, output_path).map(|_| { + let _ = std::fs::remove_file(&concat_output); + }) + }); + if let Some(ref p) = temp_cover_video { + let _ = std::fs::remove_file(p); + } + match move_result { + Ok(_) => ApiResponse { + code: 200, + message: "视频合成成功".to_string(), + data: Some(serde_json::json!({ + "outputPath": output_path, + })), + }, + Err(e) => ApiResponse { + code: 500, + message: format!("移动最终视频失败: {}", e), + data: None, + } + } + } + } + Err(e) => { + if let Some(ref p) = temp_cover_video { + let _ = std::fs::remove_file(p); + } + ApiResponse { + code: 500, + message: format!("视频拼接失败: {}", e), + data: None + } + } + } + } else { + ApiResponse { + code: 400, + message: "Missing required field: videoPaths".to_string(), + data: None + } + } +} diff --git a/tauri-app/src-tauri/tauri.conf.json b/tauri-app/src-tauri/tauri.conf.json new file mode 100644 index 0000000..f4d2f15 --- /dev/null +++ b/tauri-app/src-tauri/tauri.conf.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "美家卡智影", + "version": "0.1.0", + "identifier": "cn.meijiaka.ai-video", + "build": { + "beforeDevCommand": "npm run dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "npm run build", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "label": "main", + "title": "美家卡 智影", + "width": 1440, + "height": 960, + "minWidth": 960, + "minHeight": 640, + "resizable": true, + "visible": true + } + ], + "security": { + "csp": null, + "capabilities": [ + "default" + ], + "assetProtocol": { + "enable": true, + "scope": [ + "$APPLOCALDATA/**", + "$APPDATA/**", + "$APPCONFIG/**", + "/**" + ] + } + } + }, + "plugins": { + "opener": {} + }, + "bundle": { + "active": true, + "targets": "all", + "externalBin": [ + "binaries/ffmpeg" + ], + "resources": { + "fonts/*": "fonts/" + }, + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } +} diff --git a/tauri-app/src/App.css b/tauri-app/src/App.css new file mode 100644 index 0000000..241a169 --- /dev/null +++ b/tauri-app/src/App.css @@ -0,0 +1,21 @@ +.app-layout { + display: flex; + height: 100vh; + width: 100vw; + overflow: hidden; +} + +.app-main { + flex: 1; + margin-left: var(--sidebar-width); + height: 100vh; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.app-content { + flex: 1; + overflow: hidden auto; + padding: var(--spacing-xl); +} diff --git a/tauri-app/src/App.tsx b/tauri-app/src/App.tsx new file mode 100644 index 0000000..d1e4b82 --- /dev/null +++ b/tauri-app/src/App.tsx @@ -0,0 +1,163 @@ +/** + * App 入口 + * ======== + */ + +import { useState, createContext, useContext, useEffect } from 'react'; +import Sidebar from './components/Layout/Sidebar'; +import Login from './pages/Login/Login'; +import VideoCreation from './pages/VideoCreation'; +import MyWorks from './pages/ContentManagement/MyWorks'; +import AvatarClone from './pages/ContentManagement/AvatarClone'; +import AboutUs from './pages/Settings/AboutUs'; +import SystemUpdate from './pages/Settings/SystemUpdate'; +import ThemeSettings from './pages/Settings/ThemeSettings'; +import Profile from './pages/Profile/Profile'; +import UsageDetail from './pages/Profile/UsageDetail'; +import ToastContainer from './components/Toast/ToastContainer'; +import ProgressModal from './components/ProgressModal/ProgressModal'; +import { ErrorBoundary } from './components/ErrorBoundary'; +import ConfirmModal from './components/Modal/ConfirmModal'; +import { useAuthStore, useSettingsStore } from './store'; +import { initProjectStore } from './store/projectStore'; + +import './App.css'; + +// ============================================================ +// 导航上下文 +// ============================================================ +interface NavigationContextType { + currentPage: string; + navigate: (page: string) => void; +} + +export const NavigationContext = createContext({ + currentPage: 'video-creation', + navigate: () => {}, +}); + +export const useNavigation = () => useContext(NavigationContext); + +// 页面类型 +export type PageType = + | 'video-creation' + | 'avatar-clone' + | 'my-works' + | 'about-us' + | 'system-update' + | 'theme-settings' + | 'profile' + | 'usage-detail'; + +// 页面组件映射表(模块级别,避免每次渲染重建组件实例) +const pages: Record = { + 'video-creation': VideoCreation, + 'avatar-clone': AvatarClone, + 'my-works': MyWorks, + 'about-us': AboutUs, + 'system-update': SystemUpdate, + 'theme-settings': ThemeSettings, + profile: Profile, + 'usage-detail': UsageDetail, +}; + +/** + * 渲染页面组件 + */ +function renderPage(page: PageType): React.ReactNode { + const Page = pages[page] || VideoCreation; + return ( + + + + ); +} + +/** + * 主应用组件 + */ +function App() { + const { isAuthenticated, logout, loadFromStorage } = useAuthStore(); + const { theme } = useSettingsStore(); + const [currentPage, setCurrentPage] = useState('video-creation'); + const [showLogoutConfirm, setShowLogoutConfirm] = useState(false); + + // 应用启动时从 Tauri 文件加载认证状态 + useEffect(() => { + loadFromStorage().catch(console.error); + }, []); + + // 同步主题到 document.documentElement + useEffect(() => { + if (theme === 'system') { + document.documentElement.removeAttribute('data-theme'); + } else { + document.documentElement.setAttribute('data-theme', theme); + } + }, [theme]); + + // 初始化项目存储(登录后加载本地项目) + useEffect(() => { + if (isAuthenticated) { + initProjectStore().catch(console.error); + } + }, [isAuthenticated]); + + + + const handleLogout = () => { + logout(); + setCurrentPage('video-creation'); + setShowLogoutConfirm(false); + }; + + const handleNavigate = (page: string) => { + if (page === 'logout') { + setShowLogoutConfirm(true); + return; + } + setCurrentPage(page as PageType); + }; + + return ( + <> + {/* 登录层 - 未登录时覆盖全屏 */} + {!isAuthenticated && } + + {/* 主应用层 - 始终渲染但可能被覆盖 */} +

+ + ); +} + +export default App; diff --git a/tauri-app/src/__tests__/setup.ts b/tauri-app/src/__tests__/setup.ts new file mode 100644 index 0000000..db4f20b --- /dev/null +++ b/tauri-app/src/__tests__/setup.ts @@ -0,0 +1,30 @@ +import '@testing-library/jest-dom'; +import { vi, afterEach } from 'vitest'; + +// 模拟完整的 localStorage +const localStorageMock = { + getItem: vi.fn(), + setItem: vi.fn(), + removeItem: vi.fn(), + clear: vi.fn(), +}; + +Object.defineProperty(window, 'localStorage', { + value: localStorageMock, +}); + +// 模拟 Tauri API +vi.mock('@tauri-apps/api/core', () => ({ + invoke: vi.fn(), +})); + +// 模拟 window.__TAURI_INTERNALS__ +Object.defineProperty(window, '__TAURI_INTERNALS__', { + value: {}, + writable: true, +}); + +// 清理 mocks 每个测试后 +afterEach(() => { + vi.clearAllMocks(); +}); diff --git a/tauri-app/src/api/adapters/projectAdapter.ts b/tauri-app/src/api/adapters/projectAdapter.ts new file mode 100644 index 0000000..aa2056a --- /dev/null +++ b/tauri-app/src/api/adapters/projectAdapter.ts @@ -0,0 +1,61 @@ +import type { ScriptShot } from '../types'; +import type { SaveSegmentsParams } from '../modules/project'; + +interface BackendSegment { + sequence?: number; + id?: number; + type?: 'segment' | 'empty_shot'; + scene?: string; + prompt?: string; + voiceover?: string; + duration?: string; + videoPath?: string; + videoUrl?: string; // 七牛云视频 URL + elementId?: number; + voiceId?: string; + alignmentResult?: unknown; + burnedVideoPath?: string; + burnedAt?: number; +} + +/** + * 将后端 Project Segment 转换为前端 ScriptShot + */ +export function adaptProjectSegment(seg: unknown): ScriptShot { + const raw = seg as BackendSegment; + return { + id: typeof raw.id === 'number' ? raw.id : (typeof raw.sequence === 'number' ? raw.sequence : 1), + type: raw.type === 'empty_shot' ? 'empty_shot' : 'segment', + scene: typeof raw.scene === 'string' ? raw.scene : undefined, + voiceover: typeof raw.voiceover === 'string' ? raw.voiceover : '', + duration: typeof raw.duration === 'string' ? raw.duration : '5s', + videoPath: raw.videoPath, + videoUrl: raw.videoUrl, // 七牛云视频 URL + elementId: raw.elementId, + voiceId: raw.voiceId, + alignmentResult: raw.alignmentResult as ScriptShot['alignmentResult'], + burnedVideoPath: raw.burnedVideoPath, + burnedAt: raw.burnedAt, + }; +} + +/** + * 将前端 ScriptShot 转换为后端保存格式 + */ +export function toBackendSegments(segments: ScriptShot[]): SaveSegmentsParams['segments'] { + return segments.map(seg => ({ + id: seg.id, + sequence: seg.id, + type: seg.type, + scene: seg.scene, + voiceover: seg.voiceover, + duration: seg.duration, + videoPath: seg.videoPath, + videoUrl: seg.videoUrl, // 七牛云视频 URL + elementId: seg.elementId, + voiceId: seg.voiceId, + alignmentResult: seg.alignmentResult, + burnedVideoPath: seg.burnedVideoPath, + burnedAt: seg.burnedAt, + })); +} diff --git a/tauri-app/src/api/adapters/scriptAdapter.ts b/tauri-app/src/api/adapters/scriptAdapter.ts new file mode 100644 index 0000000..3b8d5ef --- /dev/null +++ b/tauri-app/src/api/adapters/scriptAdapter.ts @@ -0,0 +1,38 @@ +import type { ScriptShot } from '../types'; + +/** + * 将后端返回的脚本生成结果标准化为前端 ScriptShot + */ +export function adaptScriptShots(data: unknown): ScriptShot[] { + if (!Array.isArray(data)) { + throw new Error('后端返回数据格式错误:期望数组'); + } + + return data.map((item: unknown, idx) => { + if (!item || typeof item !== 'object') { + return { + id: idx + 1, + type: 'segment' as const, + scene: undefined, + voiceover: '', + duration: '5s', + }; + } + + const raw = item as Record; + // 后端返回 duration 为 int(秒),统一转换为 "5s" 格式字符串 + let duration = '5s'; + if (typeof raw.duration === 'number') { + duration = `${Math.max(1, Math.round(raw.duration))}s`; + } else if (typeof raw.duration === 'string') { + duration = raw.duration; + } + return { + id: typeof raw.id === 'number' ? raw.id : idx + 1, + type: raw.type === 'empty_shot' ? 'empty_shot' : 'segment', + scene: typeof raw.scene === 'string' ? raw.scene : undefined, + voiceover: typeof raw.voiceover === 'string' ? raw.voiceover : '', + duration, + }; + }); +} diff --git a/tauri-app/src/api/client.ts b/tauri-app/src/api/client.ts new file mode 100644 index 0000000..6fa3553 --- /dev/null +++ b/tauri-app/src/api/client.ts @@ -0,0 +1,212 @@ +import { invoke } from '@tauri-apps/api/core'; +import { ApiResponse } from './types'; + +// 优先读取 Vite 环境变量,未配置时兜底为本地 Docker API 地址(与 docker-compose 端口一致) +export const PYTHON_API_BASE_URL = + import.meta.env.VITE_API_BASE_URL || 'http://127.0.0.1:8080/api/v1'; + +// 简单的内存缓存 +type AuthData = { token?: string } | null; +let cachedAuth: AuthData = null; + +/** + * 检测是否在 Tauri 环境中运行 + */ +function isTauri(): boolean { + return typeof window !== 'undefined' && !!(window as unknown as Record).__TAURI__; +} + +/** + * 从存储加载 token(Tauri 文件存储优先,浏览器回退 localStorage) + */ +async function loadAuthToken(): Promise { + // 如果内存中有缓存,直接返回 + if (cachedAuth?.token) { + return cachedAuth.token; + } + + try { + if (isTauri()) { + const result = await invoke<{ code: number; data?: { token?: string } }>('load_auth_state'); + if (result.code === 200 && result.data?.token) { + cachedAuth = result.data; + return result.data.token; + } + } else { + // 浏览器环境:从 localStorage 读取 + const legacy = localStorage.getItem('ai-video-auth'); + if (legacy) { + try { + const parsed = JSON.parse(legacy); + const token = parsed?.state?.token; + if (token) { + cachedAuth = { token }; + return token; + } + } catch { + // 忽略解析错误 + } + } + } + } catch (e) { + console.error('[client] 加载认证 token 失败:', e); + } + + // 浏览器开发环境兜底:使用测试 token + if (!isTauri() && import.meta.env.DEV) { + const devToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMGVlYmM5OS05YzBiLTRlZjgtYmI2ZC02YmI5YmQzODBhMTEiLCJtb2JpbGUiOiIxMzgwMDEzODAwMCIsIm5pY2tuYW1lIjoiXHU2ZDRiXHU4YmQ1XHU3NTI4XHU2MjM3IiwiZXhwIjoxNzc2ODY3MDM0fQ._fl5HeX8YHaSJlWTb8s7lfHd2mLDzDZAP1NvvTQ_GpY'; + cachedAuth = { token: devToken }; + return devToken; + } + + return null; +} + +/** + * 清除 token 缓存(用于登出) + */ +export function clearAuthCache(): void { + cachedAuth = null; +} + +/** + * 转换 camelCase 对象key为 snake_case(发送给后端) + */ +function camelToSnake(obj: unknown): unknown { + if (obj === null || typeof obj !== 'object') { + return obj; + } + if (Array.isArray(obj)) { + return obj.map(item => camelToSnake(item)); + } + const result: Record = {}; + for (const [key, value] of Object.entries(obj)) { + const snakeKey = key.replace(/([A-Z])/g, '_$1').toLowerCase(); + result[snakeKey] = camelToSnake(value); + } + return result; +} + +/** + * 转换 snake_case 对象key为 camelCase(从后端接收) + */ +function snakeToCamel(obj: unknown): unknown { + if (obj === null || typeof obj !== 'object') { + return obj; + } + if (Array.isArray(obj)) { + return obj.map(item => snakeToCamel(item)); + } + const result: Record = {}; + for (const [key, value] of Object.entries(obj)) { + const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); + result[camelKey] = snakeToCamel(value); + } + return result; +} + +/** + * HTTP 直连 Python 后端 + * 自动转换:前端 camelCase ↔ 后端 snake_case + */ +async function httpRequest( + method: string, + path: string, + body?: unknown, + options?: { headers?: Record; isFormData?: boolean; cache?: RequestCache } +): Promise { + const url = `${PYTHON_API_BASE_URL}${path}`; + + // 自动添加认证头(从 Tauri 文件存储读取) + const token = await loadAuthToken(); + const fetchOptions: RequestInit = { + method, + headers: { + ...(options?.isFormData ? {} : { 'Content-Type': 'application/json' }), + ...(token ? { Authorization: `Bearer ${token}` } : {}), + ...options?.headers, + }, + cache: options?.cache, + }; + + if (body !== undefined && !options?.isFormData) { + // 自动转换 camelCase → snake_case 发给后端 + fetchOptions.body = JSON.stringify(camelToSnake(body)); + } else if (body !== undefined) { + fetchOptions.body = body as BodyInit; + } + + const response = await fetch(url, fetchOptions); + + if (!response.ok) { + const errorText = await response.text().catch(() => response.statusText); + throw new Error(extractErrorMessage(errorText, `HTTP ${response.status}: ${response.statusText}`)); + } + + const result = await response.json(); + return parseResponse(result); +} + +/** + * 解析后端响应并转换 camelCase + */ +function parseResponse(raw: string | unknown): T { + const result = typeof raw === 'string' ? JSON.parse(raw) : raw; + const convertedResult = snakeToCamel(result) as typeof result; + if (convertedResult && typeof convertedResult === 'object' && 'data' in convertedResult) { + return (convertedResult as ApiResponse).data as T; + } + return convertedResult as T; +} + +/** + * 从错误响应中提取可读消息 + */ +function extractErrorMessage(responseText: string, fallbackStatus: string): string { + let message = responseText || fallbackStatus; + try { + const errorData = JSON.parse(responseText); + if (typeof errorData.message === 'string') message = errorData.message; + else if (typeof errorData.detail === 'string') message = errorData.detail; + else if (typeof errorData.detail === 'object' && errorData.detail !== null) { + message = errorData.detail.message || errorData.detail.error || JSON.stringify(errorData.detail); + } + } catch {} + return message; +} + +/** + * HTTP API 客户端 + */ +export const client = { + async get(path: string): Promise { + const cacheBuster = `_t=${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; + const pathWithCacheBuster = path.includes('?') ? `${path}&${cacheBuster}` : `${path}?${cacheBuster}`; + return httpRequest('GET', pathWithCacheBuster, undefined, { + headers: { + 'Cache-Control': 'no-store, no-cache, must-revalidate', + Pragma: 'no-cache', + }, + }); + }, + + async post(path: string, data?: unknown): Promise { + return httpRequest('POST', path, data); + }, + + async put(path: string, data?: unknown): Promise { + return httpRequest('PUT', path, data); + }, + + async patch(path: string, data?: unknown): Promise { + return httpRequest('PATCH', path, data); + }, + + async delete(path: string): Promise { + return httpRequest('DELETE', path); + }, + + async postForm(path: string, formData: FormData): Promise { + return httpRequest('POST', path, formData, { isFormData: true }); + }, +}; diff --git a/tauri-app/src/api/generated/openapi.json b/tauri-app/src/api/generated/openapi.json new file mode 100644 index 0000000..67e13fd --- /dev/null +++ b/tauri-app/src/api/generated/openapi.json @@ -0,0 +1,8498 @@ +/Users/0fun/work/ai-meijiaka/python-api/.venv/lib/python3.13/site-packages/pydantic/_internal/_fields.py:132: UserWarning: Field "model_id" in TestModelRequest has conflict with protected namespace "model_". + +You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`. + warnings.warn( +/Users/0fun/work/ai-meijiaka/python-api/.venv/lib/python3.13/site-packages/pydantic/_internal/_fields.py:132: UserWarning: Field "model_name" in ModelResponse has conflict with protected namespace "model_". + +You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`. + warnings.warn( +/Users/0fun/work/ai-meijiaka/python-api/.venv/lib/python3.13/site-packages/pydantic/_internal/_fields.py:132: UserWarning: Field "model_id" in GenerateRequest has conflict with protected namespace "model_". + +You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`. + warnings.warn( +/Users/0fun/work/ai-meijiaka/python-api/.venv/lib/python3.13/site-packages/pydantic/_internal/_fields.py:132: UserWarning: Field "model_id" in ScriptGenerateRequest has conflict with protected namespace "model_". + +You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`. + warnings.warn( +/Users/0fun/work/ai-meijiaka/python-api/.venv/lib/python3.13/site-packages/pydantic/_internal/_fields.py:132: UserWarning: Field "model_id" in PolishRequest has conflict with protected namespace "model_". + +You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`. + warnings.warn( +{ + "openapi": "3.1.0", + "info": { + "title": "美家卡智影 API", + "description": "美家卡智影 - AI 视频创作后端 API", + "version": "0.1.0" + }, + "paths": { + "/api/v1/auth/login": { + "post": { + "tags": [ + "Authentication" + ], + "summary": "Login", + "description": "手机号登录/注册\n\n- 如果手机号已存在,返回对应用户\n- 如果不存在,自动创建新用户\n- 返回 JWT Token 用于后续认证", + "operationId": "login_api_v1_auth_login_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MobileLoginRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_LoginResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/auth/me": { + "get": { + "tags": [ + "Authentication" + ], + "summary": "Get Me", + "description": "获取当前登录用户信息", + "operationId": "get_me_api_v1_auth_me_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + } + }, + "security": [ + { + "HTTPBearer": [] + } + ] + } + }, + "/api/v1/auth/refresh": { + "post": { + "tags": [ + "Authentication" + ], + "summary": "Refresh Token", + "description": "刷新 JWT Token", + "operationId": "refresh_token_api_v1_auth_refresh_post", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + } + }, + "security": [ + { + "HTTPBearer": [] + } + ] + } + }, + "/api/v1/script/generate": { + "post": { + "tags": [ + "Script" + ], + "summary": "Generate Script", + "description": "同步生成脚本\n\n直接返回生成的分镜列表,适合快速预览。", + "operationId": "generate_script_api_v1_script_generate_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateScriptRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_Segment__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script/generate/stream": { + "post": { + "tags": [ + "Script" + ], + "summary": "Generate Script Stream", + "description": "流式生成脚本(SSE)\n\n返回 Server-Sent Events,包含进度更新和最终结果。\n前端通过 EventSource 接收实时进度。\n\n**SSE 事件类型:**\n- `start`: 开始生成\n- `analyzing`: 分析主题\n- `planning`: 规划结构\n- `generating`: AI 生成中\n- `parsing`: 解析结果\n- `complete`: 完成,包含 result 字段\n- `error`: 错误\n\n**示例事件流:**\n```\ndata: {\"type\": \"start\", \"progress\": 0, \"message\": \"开始生成脚本\"}\n\ndata: {\"type\": \"analyzing\", \"progress\": 15, \"message\": \"分析目标受众...\"}\n\ndata: {\"type\": \"complete\", \"progress\": 100, \"message\": \"成功生成 5 个分镜\", \"result\": [...]}\n```", + "operationId": "generate_script_stream_api_v1_script_generate_stream_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateScriptRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script/polish": { + "post": { + "tags": [ + "Script" + ], + "summary": "Polish Content", + "description": "AI 润色文案/画面描述\n\n- `polishType=scene`: 润色画面描述(根据 shot_type 自动区分分镜/空镜)\n- `polishType=voiceover`: 润色配音文案\n\n参数:\n- `shot_type`: \"segment\"(分镜)或 \"empty_shot\"(空镜),画面润色时必填", + "operationId": "polish_content_api_v1_script_polish_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/app__schemas__script__PolishRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_str_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/script/model-health": { + "get": { + "tags": [ + "Script" + ], + "summary": "Check Model Health", + "description": "检查 AI 模型健康状态\n\n返回所有配置的模型及其可用性状态。", + "operationId": "check_model_health_api_v1_script_model_health_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ModelHealthResponse_" + } + } + } + } + } + } + }, + "/api/v1/script/test-model": { + "post": { + "tags": [ + "Script" + ], + "summary": "Test Model", + "description": "测试指定模型连接\n\n发送一个简单的测试请求,验证模型是否可用。", + "operationId": "test_model_api_v1_script_test_model_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TestModelRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TestModelResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/ai/platforms": { + "get": { + "tags": [ + "AI Models" + ], + "summary": "List Platforms", + "description": "获取所有平台列表", + "operationId": "list_platforms_api_v1_ai_platforms_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_PlatformResponse__" + } + } + } + } + } + } + }, + "/api/v1/ai/models": { + "get": { + "tags": [ + "AI Models" + ], + "summary": "List Models", + "description": "获取模型列表\n\nArgs:\n capability: 按能力过滤,如 script、polish、chat", + "operationId": "list_models_api_v1_ai_models_get", + "parameters": [ + { + "name": "capability", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Capability" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_ModelResponse__" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/ai/generate": { + "post": { + "tags": [ + "AI Models" + ], + "summary": "Generate Text", + "description": "文本生成(自动路由到对应平台)", + "operationId": "generate_text_api_v1_ai_generate_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_GenerateResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/ai/health": { + "get": { + "tags": [ + "AI Models" + ], + "summary": "Health Check", + "description": "检查模型健康状态", + "operationId": "health_check_api_v1_ai_health_get", + "parameters": [ + { + "name": "model_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_HealthResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/ai/platforms/{platform_id}/test": { + "get": { + "tags": [ + "AI Models" + ], + "summary": "Test Platform Connection", + "description": "测试平台连接", + "operationId": "test_platform_connection_api_v1_ai_platforms__platform_id__test_get", + "parameters": [ + { + "name": "platform_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Platform Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/ai/reload": { + "post": { + "tags": [ + "AI Models" + ], + "summary": "Reload Config", + "description": "重新加载配置文件", + "operationId": "reload_config_api_v1_ai_reload_post", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + } + } + } + }, + "/api/v1/ai/prompts/templates": { + "get": { + "tags": [ + "AI Models" + ], + "summary": "Get Prompt Templates", + "description": "获取所有可用的 Prompt 模板配置\n\n包括脚本类型、视频风格、语气风格等选项。", + "operationId": "get_prompt_templates_api_v1_ai_prompts_templates_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PromptTemplatesResponse_" + } + } + } + } + } + } + }, + "/api/v1/ai/prompts/build": { + "post": { + "tags": [ + "AI Models" + ], + "summary": "Build System Prompt", + "description": "构建系统 Prompt(用于调试和预览)\n\n返回构建好的系统 Prompt,可用于前端预览或调试。", + "operationId": "build_system_prompt_api_v1_ai_prompts_build_post", + "parameters": [ + { + "name": "duration", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 30, + "title": "Duration" + } + }, + { + "name": "script_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "干货型", + "title": "Script Type" + } + }, + { + "name": "video_style", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "口播", + "title": "Video Style" + } + }, + { + "name": "tone", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Tone" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/ai/scripts/generate": { + "post": { + "tags": [ + "AI Models" + ], + "summary": "Generate Script", + "description": "生成家装行业短视频脚本\n\n使用专业的 Prompt 模板生成包含分镜+空镜的混合脚本。\n针对前端展示优化,返回分镜数、空镜数、总字数等统计信息。", + "operationId": "generate_script_api_v1_ai_scripts_generate_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScriptGenerateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ScriptGenerateResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/ai/scripts/polish": { + "post": { + "tags": [ + "AI Models" + ], + "summary": "Polish Script Content", + "description": "润色脚本内容\n\n对场景描述或口播文案进行专业润色。", + "operationId": "polish_script_content_api_v1_ai_scripts_polish_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/app__api__v1__ai_models__PolishRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PolishResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/videos/omni": { + "post": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Create Omni Video", + "description": "Omni-Video 多模态视频生成\n\n支持文本、图片、主体、视频等多种输入方式组合生成视频。\n适用于 kling-v3-omni 和 kling-video-o1 模型。\n\n**特性:**\n- 支持 3-15 秒视频生成\n- 支持多镜头和智能分镜 (shotType=intelligence)\n- 支持引用主体、图片、视频作为参考\n- 支持 <<>>/<<>>/<<>> 语法引用提示词中的资源", + "operationId": "create_omni_video_api_v1_klingai_videos_omni_post", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OmniVideoRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/app__schemas__common__ApiResponse_VideoGenerateResponse___1" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "get": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "List Omni Video Tasks", + "description": "查询 Omni-Video 任务列表\n\n查询历史 Omni-Video 任务列表。", + "operationId": "list_omni_video_tasks_api_v1_klingai_videos_omni_get", + "parameters": [ + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 30, + "title": "Page Size" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/videos/omni/{task_id}": { + "get": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Get Omni Video Task", + "description": "查询 Omni-Video 任务状态\n\n查询指定 Omni-Video 任务的执行状态和结果。", + "operationId": "get_omni_video_task_api_v1_klingai_videos_omni__task_id__get", + "parameters": [ + { + "name": "task_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Task Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskStatusResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/videos/image2video": { + "post": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Create Image To Video", + "description": "图生视频\n\n根据输入图片生成视频。", + "operationId": "create_image_to_video_api_v1_klingai_videos_image2video_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Image2VideoRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/app__schemas__common__ApiResponse_VideoGenerateResponse___1" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/videos/extend": { + "post": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Extend Video", + "description": "视频延长\n\n延长现有视频的时长。", + "operationId": "extend_video_api_v1_klingai_videos_extend_post", + "parameters": [ + { + "name": "video_id", + "in": "query", + "required": true, + "schema": { + "type": "string", + "title": "Video Id" + } + }, + { + "name": "prompt", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Prompt" + } + }, + { + "name": "duration", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 5, + "title": "Duration" + } + }, + { + "name": "callback_url", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Callback Url" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/app__schemas__common__ApiResponse_VideoGenerateResponse___1" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/videos/identify-face": { + "post": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Identify Face", + "description": "对口型前置:人脸识别\n\n分析视频中的人脸信息,返回 sessionId 和 faceId,用于后续的 advanced-lip-sync。", + "operationId": "identify_face_api_v1_klingai_videos_identify_face_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IdentifyFaceRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_IdentifyFaceResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/videos/advanced-lip-sync": { + "post": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Create Advanced Lip Sync", + "description": "新版对口型视频生成\n\n基于 KlingAI advanced-lip-sync 接口,先调用 /videos/identify-face 获取 sessionId 和 faceId,\n再传入本接口生成对口型视频。\n\n支持 audio_id(TTS 生成)或 soundFile(外部音频 URL)驱动口型。", + "operationId": "create_advanced_lip_sync_api_v1_klingai_videos_advanced_lip_sync_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdvancedLipSyncRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/app__schemas__common__ApiResponse_VideoGenerateResponse___1" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/videos/advanced-lip-sync/{taskId}": { + "get": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Get Advanced Lip Sync Task", + "description": "查询新版对口型任务状态", + "operationId": "get_advanced_lip_sync_task_api_v1_klingai_videos_advanced_lip_sync__taskId__get", + "parameters": [ + { + "name": "taskId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Taskid" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskStatusResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/images/omni": { + "post": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Create Omni Image", + "description": "Omni-Image 图像生成\n\n支持文本、主体、参考图等多种输入方式组合生成图像。", + "operationId": "create_omni_image_api_v1_klingai_images_omni_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OmniImageRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/app__schemas__common__ApiResponse_VideoGenerateResponse___1" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/images/omni/{task_id}": { + "get": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Get Omni Image Task", + "description": "查询 Omni-Image 任务状态", + "operationId": "get_omni_image_task_api_v1_klingai_images_omni__task_id__get", + "parameters": [ + { + "name": "task_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Task Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskStatusResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/images/generations": { + "post": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Create Image", + "description": "文生图\n\n根据文本描述生成图像。", + "operationId": "create_image_api_v1_klingai_images_generations_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImageGenerateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/app__schemas__common__ApiResponse_VideoGenerateResponse___1" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/virtual-tryon": { + "post": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Create Virtual Tryon", + "description": "虚拟试穿\n\n将衣服虚拟试穿到人物身上。", + "operationId": "create_virtual_tryon_api_v1_klingai_virtual_tryon_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VirtualTryonRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/app__schemas__common__ApiResponse_VideoGenerateResponse___1" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/tasks/{taskId}": { + "get": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Get Taskstatus", + "description": "查询任务状态\n\n查询指定任务的执行状态和结果。\n\nArgs:\n taskId: 任务 ID\n task_type: 任务类型 (video, image2video, image, lip-sync, virtual-tryon)", + "operationId": "get_taskStatus_api_v1_klingai_tasks__taskId__get", + "parameters": [ + { + "name": "taskId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Taskid" + } + }, + { + "name": "task_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "video", + "title": "Task Type" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TaskStatusResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/tasks": { + "get": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "List Tasks", + "description": "查询任务列表\n\n查询历史任务列表。", + "operationId": "list_tasks_api_v1_klingai_tasks_get", + "parameters": [ + { + "name": "task_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "video", + "title": "Task Type" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 1, + "title": "Page" + } + }, + { + "name": "page_size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 10, + "title": "Page Size" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/elements": { + "get": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "List Elements", + "description": "查询主体列表\n\n获取所有已创建的主体(自定义元素)列表。", + "operationId": "list_elements_api_v1_klingai_elements_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_ElementResponse__" + } + } + } + } + } + }, + "post": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Create Element", + "description": "创建主体(自定义元素)\n\n通过上传图片或视频创建可复用的主体,用于视频/图像生成时保持角色一致性。\n\n图片要求:\n- 格式:jpg, jpeg, png\n- 大小:≤10MB\n- 数量:正面图 + 1-3张其他角度\n\n视频要求:\n- 格式:mp4, mov\n- 时长:3-8秒\n- 分辨率:1080P\n- 大小:≤200MB", + "operationId": "create_element_api_v1_klingai_elements_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateElementRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ElementResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/elements/{elementId}": { + "get": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Get Element", + "description": "查询单个主体详情\n\n获取指定主体的详细信息。", + "operationId": "get_element_api_v1_klingai_elements__elementId__get", + "parameters": [ + { + "name": "elementId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Elementid" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_ElementResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Delete Element", + "description": "删除主体\n\n删除不再使用的主体(自定义元素)。", + "operationId": "delete_element_api_v1_klingai_elements__elementId__delete", + "parameters": [ + { + "name": "elementId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Elementid" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/elements/ai-multi-shot": { + "post": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Ai Multishot", + "description": "智能补全主体不同角度图片\n\n通过主体正面图,自动推理出该主体其他角度图片。\n每次可生成3组结果供选择,每次扣减0.5积分。\n\n使用流程:\n1. 调用此接口传入正面图\n2. 轮询查询任务状态\n3. 获取生成的多组角度图片\n4. 选择合适的图片创建主体", + "operationId": "ai_multiShot_api_v1_klingai_elements_ai_multi_shot_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiMultiShotRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AiMultiShotResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/elements/ai-multi-shot/{taskId}": { + "get": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Get Ai Multishot Task", + "description": "查询智能补全主体图任务状态\n\n获取指定任务的执行状态和生成的多角度图片结果。", + "operationId": "get_ai_multiShot_task_api_v1_klingai_elements_ai_multi_shot__taskId__get", + "parameters": [ + { + "name": "taskId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Taskid" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/voices/custom": { + "get": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "List Custom Voices", + "description": "查询自定义音色列表\n\n获取所有已创建的自定义音色。", + "operationId": "list_custom_voices_api_v1_klingai_voices_custom_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_VoiceInfo__" + } + } + } + } + } + }, + "post": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Create Custom Voice", + "description": "创建自定义音色\n\n通过上传音频文件或引用历史视频创建自定义音色,用于对口型视频。\n\n音频要求:\n- 格式:mp3, wav, mp4, mov\n- 时长:5-30 秒\n- 人声干净、无杂音、单一人声", + "operationId": "create_custom_voice_api_v1_klingai_voices_custom_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCustomVoiceRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_CreateCustomVoiceResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/voices/custom/{voiceId}": { + "get": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Get Custom Voice", + "description": "查询单个自定义音色\n\n获取指定自定义音色的详细信息。", + "operationId": "get_custom_voice_api_v1_klingai_voices_custom__voiceId__get", + "parameters": [ + { + "name": "voiceId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Voiceid" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "Delete Custom Voice", + "description": "删除自定义音色\n\n删除不再使用的自定义音色。", + "operationId": "delete_custom_voice_api_v1_klingai_voices_custom__voiceId__delete", + "parameters": [ + { + "name": "voiceId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Voiceid" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/klingai/voices/presets": { + "get": { + "tags": [ + "KlingAI", + "KlingAI" + ], + "summary": "List Preset Voices", + "description": "查询官方预设音色列表\n\n获取 KlingAI 提供的官方音色列表。", + "operationId": "list_preset_voices_api_v1_klingai_voices_presets_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_VoiceInfo__" + } + } + } + } + } + } + }, + "/api/v1/qiniu/upload-token": { + "post": { + "tags": [ + "Qiniu Storage", + "Qiniu Storage" + ], + "summary": "Get Upload Token", + "description": "获取上传凭证(客户端直传)\n\n前端获取 Token 后,可直接上传到七牛云,无需经过服务端。\n\n上传地址: https://upload.qiniup.com\n请求方式: POST (multipart/form-data)\n请求参数:\n - token: 上传凭证(本接口返回)\n - key: 文件存储 Key(本接口返回)\n - file: 文件内容", + "operationId": "get_upload_token_api_v1_qiniu_upload_token_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UploadTokenRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_UploadTokenResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/qiniu/upload/audio": { + "post": { + "tags": [ + "Qiniu Storage", + "Qiniu Storage" + ], + "summary": "Upload Audio", + "description": "上传音频文件\n\n支持格式: MP3, WAV, M4A, AAC, OGG\n文件会自动存储到: audios/{userId}/{date}/{uuid}.{ext}", + "operationId": "upload_audio_api_v1_qiniu_upload_audio_post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_upload_audio_api_v1_qiniu_upload_audio_post" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_FileUploadResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/qiniu/upload/video": { + "post": { + "tags": [ + "Qiniu Storage", + "Qiniu Storage" + ], + "summary": "Upload Video", + "description": "上传视频文件\n\n支持格式: MP4, MOV, AVI, WebM\n文件会自动存储到: videos/{userId}/{date}/{uuid}.{ext}", + "operationId": "upload_video_api_v1_qiniu_upload_video_post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_upload_video_api_v1_qiniu_upload_video_post" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_FileUploadResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/qiniu/upload/avatar": { + "post": { + "tags": [ + "Qiniu Storage", + "Qiniu Storage" + ], + "summary": "Upload Avatar", + "description": "上传形象克隆视频\n\n用于形象克隆功能,上传的视频将同时用于创建自定义音色和主体。\n\nKlingAI 要求:\n - 格式: MP4, MOV\n - 时长: 5-30 秒 (建议 5-8 秒)\n - 大小: 不超过 200MB\n - 分辨率: 高度 720px~2160px\n - 内容: 写实风格人物正面特写,人脸清晰、无遮挡,视频中有清晰人声\n\n文件存储路径: meijiaka/avatars/{userId}/{date}/{uuid}.{ext}\n\n重复检测:\n - 如果提供了 fileHash,会检查是否已有相同文件的任务在进行中\n - 返回的 isDuplicate 表示是否复用了已有资源\n - existingTaskId 表示已存在任务的ID(如果有)", + "operationId": "upload_avatar_api_v1_qiniu_upload_avatar_post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_upload_avatar_api_v1_qiniu_upload_avatar_post" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_FileUploadResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBearer": [] + } + ] + } + }, + "/api/v1/qiniu/files/{key}": { + "get": { + "tags": [ + "Qiniu Storage", + "Qiniu Storage" + ], + "summary": "Get File Info", + "description": "获取文件信息\n\nArgs:\n key: 文件存储 Key(路径格式)", + "operationId": "get_file_info_api_v1_qiniu_files__key__get", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Key" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Qiniu Storage", + "Qiniu Storage" + ], + "summary": "Delete File", + "description": "删除文件\n\nArgs:\n key: 文件存储 Key", + "operationId": "delete_file_api_v1_qiniu_files__key__delete", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Key" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/qiniu/refresh-cdn": { + "post": { + "tags": [ + "Qiniu Storage", + "Qiniu Storage" + ], + "summary": "Refresh Cdn", + "description": "刷新 CDN 缓存\n\n文件更新后,调用此接口刷新 CDN 缓存,确保用户访问到最新内容。", + "operationId": "refresh_cdn_api_v1_qiniu_refresh_cdn_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Keys" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/video/generate": { + "post": { + "tags": [ + "Video", + "Video" + ], + "summary": "Create Video Generation", + "description": "创建视频生成任务\n\n接收项目ID、数字人ID和分镜列表,创建视频生成作业。\n支持 SSE 流式查询进度。\n\n**分镜类型说明:**\n- `segment`: 分镜(带数字人),使用 omni-video 接口,需要 human_id\n- `empty_shot`: 空镜,使用文生图 + 图生视频流程\n\n**调用流程:**\n1. 调用此接口创建任务,获取 job_id\n2. 使用 SSE 接口 `/video/jobs/{job_id}/stream` 监听进度\n3. 或使用 `/video/jobs/{job_id}` 查询状态", + "operationId": "create_video_generation_api_v1_video_generate_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VideoGenerateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/app__schemas__common__ApiResponse_VideoGenerateResponse___2" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/video/jobs/{job_id}": { + "get": { + "tags": [ + "Video", + "Video" + ], + "summary": "Get Video Job", + "description": "查询视频生成作业详情\n\n获取指定作业的详细信息和所有分镜的处理结果。", + "operationId": "get_video_job_api_v1_video_jobs__job_id__get", + "parameters": [ + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Job Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_VideoJobDetail_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/video/jobs/{job_id}/stream": { + "get": { + "tags": [ + "Video", + "Video" + ], + "summary": "Stream Video Job", + "description": "SSE 流式获取视频生成进度\n\n使用 Server-Sent Events 实时推送视频生成进度。\n\n**事件类型:**\n- `start`: 开始生成\n- `processing`: 处理中(包含进度信息)\n- `finalizing`: 完成整理\n- `complete`: 全部完成\n- `error`: 发生错误\n\n**示例:**\n```\nconst eventSource = new EventSource('/api/v1/video/jobs/{job_id}/stream');\neventSource.onmessage = (e) => {\n const data = JSON.parse(e.data);\n console.log(data.progress + '%: ' + data.message);\n};\n```", + "operationId": "stream_video_job_api_v1_video_jobs__job_id__stream_get", + "parameters": [ + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Job Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/video/jobs/{job_id}/status": { + "get": { + "tags": [ + "Video", + "Video" + ], + "summary": "Get Video Job Status", + "description": "获取视频生成作业状态(简化版)", + "operationId": "get_video_job_status_api_v1_video_jobs__job_id__status_get", + "parameters": [ + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Job Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_VideoJobStatus_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/video/library": { + "get": { + "tags": [ + "Video", + "Video" + ], + "summary": "Get Digital Humans", + "description": "获取数字人素材库\n\n返回系统预设的数字人列表。", + "operationId": "get_digital_humans_api_v1_video_library_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_DigitalHuman__" + } + } + } + } + } + } + }, + "/api/v1/video/upload": { + "post": { + "tags": [ + "Video", + "Video" + ], + "summary": "Upload Video", + "description": "上传人物视频作为数字人素材\n\n文件要求:\n- 格式:mp4, mov\n- 时长:2-60秒\n- 分辨率:720p 或 1080p", + "operationId": "upload_video_api_v1_video_upload_post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_upload_video_api_v1_video_upload_post" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_DigitalHuman_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/video/{video_id}/download": { + "get": { + "tags": [ + "Video", + "Video" + ], + "summary": "Download Video", + "description": "下载视频文件\n\n支持三种查找位置:\n1. data/video/{video_id}.mp4 - 传统存储\n2. data/uploads/{video_id}.ext - 上传文件\n3. ~/Documents/Meijiaka/projects/*/videos/{video_id}.mp4 - 项目生成的视频\n 文件名格式: scene_{shot_id}.mp4", + "operationId": "download_video_api_v1_video__video_id__download_get", + "parameters": [ + { + "name": "video_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Video Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/video/{video_id}/thumbnail": { + "get": { + "tags": [ + "Video", + "Video" + ], + "summary": "Get Video Thumbnail", + "description": "获取视频缩略图", + "operationId": "get_video_thumbnail_api_v1_video__video_id__thumbnail_get", + "parameters": [ + { + "name": "video_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Video Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/avatar/clone": { + "post": { + "tags": [ + "Avatar" + ], + "summary": "Clone Avatar", + "description": "提交形象克隆任务\n\n立即返回 task_id,前端通过 SSE 或轮询跟踪进度。\n实际串行流程由 Async Engine Scheduler 异步执行。", + "operationId": "clone_avatar_api_v1_avatar_clone_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CloneAvatarRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_CloneAvatarResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBearer": [] + } + ] + } + }, + "/api/v1/avatar/tasks/{task_id}": { + "get": { + "tags": [ + "Avatar" + ], + "summary": "Get Avatar Task Status", + "description": "查询形象克隆任务状态", + "operationId": "get_avatar_task_status_api_v1_avatar_tasks__task_id__get", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "task_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Task Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AvatarTaskStatusResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/avatar/clone/stream": { + "get": { + "tags": [ + "Avatar" + ], + "summary": "Sse Avatar Clone", + "description": "SSE 流:实时推送形象克隆任务状态\n\n前端连接后,每 3 秒推送一次状态,直到任务结束(succeed / failed / timeout)。", + "operationId": "sse_avatar_clone_api_v1_avatar_clone_stream_get", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "task_id", + "in": "query", + "required": true, + "schema": { + "type": "string", + "description": "任务 ID", + "title": "Task Id" + }, + "description": "任务 ID" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/avatar/tasks/{task_id}/retry": { + "post": { + "tags": [ + "Avatar" + ], + "summary": "Retry Avatar Task", + "description": "重试失败或超时的形象克隆任务\n\n仅允许对 voice_failed / element_failed / timeout 状态的任务重试。\n重试时会重置状态为 pending 并重新注册到 Async Engine。", + "operationId": "retry_avatar_task_api_v1_avatar_tasks__task_id__retry_post", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "task_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Task Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/avatar/{avatar_id}": { + "delete": { + "tags": [ + "Avatar" + ], + "summary": "Delete Avatar", + "description": "删除形象:软删除 DB 记录 + 异步清理 Kling 资源", + "operationId": "delete_avatar_api_v1_avatar__avatar_id__delete", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "avatar_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Avatar Id" + } + }, + { + "name": "voice_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Voice Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Avatar" + ], + "summary": "Update Avatar Name", + "description": "更新形象名称\n\n同步更新本地和后端 DB,保证数据一致。", + "operationId": "update_avatar_name_api_v1_avatar__avatar_id__patch", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "avatar_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Avatar Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAvatarNameRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/avatar/library": { + "get": { + "tags": [ + "Avatar" + ], + "summary": "Get Avatar Library", + "description": "获取当前用户的克隆形象库\n\n返回 DB 中状态为 succeed 且未软删除的记录,供前端与 localStorage 合并。", + "operationId": "get_avatar_library_api_v1_avatar_library_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_list_AvatarItem__" + } + } + } + } + }, + "security": [ + { + "HTTPBearer": [] + } + ] + } + }, + "/api/v1/avatar/health": { + "get": { + "tags": [ + "Avatar" + ], + "summary": "Get Avatar Health", + "description": "获取形象克隆服务健康状态\n\n普通用户只能看到自己的任务统计,管理员可以看到全部。", + "operationId": "get_avatar_health_api_v1_avatar_health_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AvatarHealthResponse_" + } + } + } + } + }, + "security": [ + { + "HTTPBearer": [] + } + ] + } + }, + "/api/v1/avatar/admin/trigger-recovery": { + "post": { + "tags": [ + "Avatar" + ], + "summary": "Admin Trigger Recovery", + "description": "手动触发卡住任务恢复(管理员接口)\n\nAsync Engine 会自动轮询,无需手动触发恢复。\n此接口保留用于向后兼容和手动排查。", + "operationId": "admin_trigger_recovery_api_v1_avatar_admin_trigger_recovery_post", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + } + }, + "security": [ + { + "HTTPBearer": [] + } + ] + } + }, + "/api/v1/system/health": { + "get": { + "tags": [ + "System" + ], + "summary": "System Health", + "description": "系统健康检查(详细版)", + "operationId": "system_health_api_v1_system_health_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + } + } + } + }, + "/api/v1/system/version": { + "get": { + "tags": [ + "System" + ], + "summary": "System Version", + "description": "获取系统版本信息", + "operationId": "system_version_api_v1_system_version_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + } + } + } + }, + "/api/v1/caption/submit": { + "post": { + "tags": [ + "Caption", + "Caption" + ], + "summary": "Submit Caption Task", + "description": "提交字幕生成任务\n\n提交音频/视频文件URL,生成带时间轴的字幕。", + "operationId": "submit_caption_task_api_v1_caption_submit_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaptionSubmitRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_CaptionTaskResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/caption/query/{task_id}": { + "get": { + "tags": [ + "Caption", + "Caption" + ], + "summary": "Query Caption Task", + "description": "查询字幕任务结果\n\nArgs:\n task_id: 任务ID\n blocking: 是否阻塞等待结果 (默认True)", + "operationId": "query_caption_task_api_v1_caption_query__task_id__get", + "parameters": [ + { + "name": "task_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Task Id" + } + }, + { + "name": "blocking", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": true, + "title": "Blocking" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_CaptionResult_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/caption/generate": { + "post": { + "tags": [ + "Caption", + "Caption" + ], + "summary": "Generate Caption", + "description": "生成字幕(完整流程)\n\n提交任务并轮询结果,直接返回最终字幕数据。\n适用于不需要异步处理的场景。", + "operationId": "generate_caption_api_v1_caption_generate_post", + "parameters": [ + { + "name": "max_wait_time", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 120, + "title": "Max Wait Time" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaptionSubmitRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_CaptionResult_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/caption/generate-ass": { + "post": { + "tags": [ + "Caption", + "Caption" + ], + "summary": "Generate Ass", + "description": "生成 ASS 格式字幕(完整流程,使用抖音美好体)\n\nArgs:\n video_width: 视频宽度(默认 1080)\n video_height: 视频高度(默认 1920)", + "operationId": "generate_ass_api_v1_caption_generate_ass_post", + "parameters": [ + { + "name": "video_width", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 1080, + "title": "Video Width" + } + }, + { + "name": "video_height", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 1920, + "title": "Video Height" + } + }, + { + "name": "max_wait_time", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 120, + "title": "Max Wait Time" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaptionSubmitRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/caption/generate-srt": { + "post": { + "tags": [ + "Caption", + "Caption" + ], + "summary": "Generate Srt", + "description": "生成 SRT 格式字幕(完整流程)\n\n直接返回 SRT 格式字幕文件内容。", + "operationId": "generate_srt_api_v1_caption_generate_srt_post", + "parameters": [ + { + "name": "max_wait_time", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 120, + "title": "Max Wait Time" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaptionSubmitRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_SrtSubtitleResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/caption/ata/submit": { + "post": { + "tags": [ + "Caption", + "Caption" + ], + "summary": "Submit Auto Align Task", + "description": "提交自动字幕打轴任务\n\n为已有字幕文本自动配上时间轴。", + "operationId": "submit_auto_align_task_api_v1_caption_ata_submit_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutoAlignSubmitRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_CaptionTaskResponse_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/caption/ata/query/{task_id}": { + "get": { + "tags": [ + "Caption", + "Caption" + ], + "summary": "Query Auto Align Task", + "description": "查询打轴任务结果", + "operationId": "query_auto_align_task_api_v1_caption_ata_query__task_id__get", + "parameters": [ + { + "name": "task_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Task Id" + } + }, + { + "name": "blocking", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": true, + "title": "Blocking" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_AutoAlignResult_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/caption/ata/align": { + "post": { + "tags": [ + "Caption", + "Caption" + ], + "summary": "Auto Align Caption", + "description": "自动字幕打轴(完整流程)\n\n提交打轴任务并轮询结果,直接返回最终数据。", + "operationId": "auto_align_caption_api_v1_caption_ata_align_post", + "parameters": [ + { + "name": "max_wait_time", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 120, + "title": "Max Wait Time" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutoAlignSubmitRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/caption/convert/ass": { + "post": { + "tags": [ + "Caption", + "Caption" + ], + "summary": "Convert To Ass", + "description": "将字幕结果转换为 ASS 格式(使用抖音美好体)", + "operationId": "convert_to_ass_api_v1_caption_convert_ass_post", + "parameters": [ + { + "name": "video_width", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 1080, + "title": "Video Width" + } + }, + { + "name": "video_height", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 1920, + "title": "Video Height" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaptionResult-Input" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/caption/convert/srt": { + "post": { + "tags": [ + "Caption", + "Caption" + ], + "summary": "Convert To Srt", + "description": "将字幕结果转换为 SRT 格式\n\n用于将 /generate 返回的原始数据转换为 SRT 格式。", + "operationId": "convert_to_srt_api_v1_caption_convert_srt_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaptionResult-Input" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/caption/convert/vtt": { + "post": { + "tags": [ + "Caption", + "Caption" + ], + "summary": "Convert To Vtt", + "description": "将字幕结果转换为 WebVTT 格式", + "operationId": "convert_to_vtt_api_v1_caption_convert_vtt_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaptionResult-Input" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_dict_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/tasks/{task_type}": { + "post": { + "tags": [ + "Tasks", + "Tasks" + ], + "summary": "Create Task", + "description": "创建新任务\n\n根据任务类型写入 Redis,由 Async Engine Scheduler 统一调度。", + "operationId": "create_task_api_v1_tasks__task_type__post", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "task_type", + "in": "path", + "required": true, + "schema": { + "enum": [ + "video", + "image", + "script", + "subtitle", + "copy", + "avatar_clone" + ], + "type": "string", + "title": "Task Type" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskCreateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskCreateResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/tasks/{task_id}": { + "get": { + "tags": [ + "Tasks", + "Tasks" + ], + "summary": "Get Task Status", + "description": "查询任务状态\n\n前端通过轮询此接口获取任务进度", + "operationId": "get_task_status_api_v1_tasks__task_id__get", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "task_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Task Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/app__api__v1__tasks__TaskStatusResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/v1/tasks/{task_id}/result": { + "get": { + "tags": [ + "Tasks", + "Tasks" + ], + "summary": "Get Task Result", + "description": "获取任务结果(简化接口,直接返回 result 字段)", + "operationId": "get_task_result_api_v1_tasks__task_id__result_get", + "security": [ + { + "HTTPBearer": [] + } + ], + "parameters": [ + { + "name": "task_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Task Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "title": "Response Get Task Result Api V1 Tasks Task Id Result Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/health": { + "get": { + "tags": [ + "System" + ], + "summary": "Health Check", + "description": "服务健康检查", + "operationId": "health_check_health_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/": { + "get": { + "tags": [ + "System" + ], + "summary": "Root", + "description": "API 根路径", + "operationId": "root__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AdvancedLipSyncRequest": { + "properties": { + "session_id": { + "type": "string", + "title": "Session Id", + "description": "人脸识别返回的会话ID" + }, + "face_choose": { + "items": { + "$ref": "#/components/schemas/FaceChooseItem" + }, + "type": "array", + "title": "Face Choose", + "description": "人脸对口型配置列表" + }, + "callback_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Callback Url", + "description": "回调通知地址" + } + }, + "type": "object", + "required": [ + "session_id", + "face_choose" + ], + "title": "AdvancedLipSyncRequest", + "description": "新版对口型请求(advanced-lip-sync)" + }, + "AiMultiShotRequest": { + "properties": { + "frontal_image": { + "type": "string", + "title": "Frontal Image", + "description": "主体正面参考图 URL", + "example": "https://example.com/front.jpg" + }, + "callback_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Callback Url", + "description": "回调通知地址" + } + }, + "type": "object", + "required": [ + "frontal_image" + ], + "title": "AiMultiShotRequest", + "description": "智能补全主体图请求" + }, + "AiMultiShotResponse": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id" + }, + "task_status": { + "type": "string", + "title": "Task Status" + }, + "created_at": { + "type": "integer", + "title": "Created At" + }, + "updated_at": { + "type": "integer", + "title": "Updated At" + } + }, + "type": "object", + "required": [ + "task_id", + "task_status", + "created_at", + "updated_at" + ], + "title": "AiMultiShotResponse", + "description": "智能补全主体图响应" + }, + "ApiResponse_AiMultiShotResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/AiMultiShotResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[AiMultiShotResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_AutoAlignResult_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/AutoAlignResult" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[AutoAlignResult]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_AvatarHealthResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/AvatarHealthResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[AvatarHealthResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_AvatarTaskStatusResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/AvatarTaskStatusResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[AvatarTaskStatusResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_CaptionResult_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/CaptionResult-Output" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[CaptionResult]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_CaptionTaskResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/CaptionTaskResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[CaptionTaskResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_CloneAvatarResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/CloneAvatarResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[CloneAvatarResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_CreateCustomVoiceResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/CreateCustomVoiceResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[CreateCustomVoiceResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_DigitalHuman_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/DigitalHuman" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[DigitalHuman]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_ElementResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ElementResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[ElementResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_FileUploadResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/FileUploadResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[FileUploadResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_GenerateResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/GenerateResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[GenerateResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_HealthResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/HealthResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[HealthResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_IdentifyFaceResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/IdentifyFaceResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[IdentifyFaceResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_LoginResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/LoginResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[LoginResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_ModelHealthResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelHealthResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[ModelHealthResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_PolishResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PolishResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[PolishResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_PromptTemplatesResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/PromptTemplatesResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[PromptTemplatesResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_ScriptGenerateResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/ScriptGenerateResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[ScriptGenerateResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_SrtSubtitleResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/SrtSubtitleResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[SrtSubtitleResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_TaskStatusResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/app__api__v1__klingai__TaskStatusResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[TaskStatusResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_TestModelResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/TestModelResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[TestModelResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_UploadTokenResponse_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/UploadTokenResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[UploadTokenResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_VideoJobDetail_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/VideoJobDetail" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[VideoJobDetail]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_VideoJobStatus_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/VideoJobStatus" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[VideoJobStatus]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_dict_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[dict]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_list_AvatarItem__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/AvatarItem" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[list[AvatarItem]]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_list_DigitalHuman__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/DigitalHuman" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[list[DigitalHuman]]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_list_ElementResponse__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/ElementResponse" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[list[ElementResponse]]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_list_ModelResponse__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/ModelResponse" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[list[ModelResponse]]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_list_PlatformResponse__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/PlatformResponse" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[list[PlatformResponse]]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_list_Segment__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/Segment" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[list[Segment]]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_list_VoiceInfo__": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/VoiceInfo" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[list[VoiceInfo]]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "ApiResponse_str_": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Data", + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[str]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "AutoAlignResult": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码: 0=成功, 2000=处理中" + }, + "message": { + "type": "string", + "title": "Message", + "description": "状态信息" + }, + "duration": { + "type": "number", + "title": "Duration", + "description": "音频时长(秒)" + }, + "utterances": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/CaptionUtterance" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Utterances", + "description": "打轴后的字幕时间轴" + } + }, + "type": "object", + "required": [ + "code", + "message", + "duration" + ], + "title": "AutoAlignResult", + "description": "自动字幕打轴结果" + }, + "AutoAlignSubmitRequest": { + "properties": { + "audio_url": { + "type": "string", + "title": "Audio Url", + "description": "音频/视频文件URL" + }, + "audio_text": { + "type": "string", + "title": "Audio Text", + "description": "要打轴的字幕文本" + }, + "caption_type": { + "type": "string", + "title": "Caption Type", + "description": "识别类型: speech(说话), singing(歌词)", + "default": "speech" + }, + "sta_punc_mode": { + "type": "integer", + "maximum": 3.0, + "minimum": 1.0, + "title": "Sta Punc Mode", + "description": "标点模式: 1=省略句末, 2=空格代替, 3=保留完整", + "default": 3 + } + }, + "type": "object", + "required": [ + "audio_url", + "audio_text" + ], + "title": "AutoAlignSubmitRequest", + "description": "自动字幕打轴任务提交请求" + }, + "AvatarHealthResponse": { + "properties": { + "total_processing": { + "type": "integer", + "title": "Total Processing", + "description": "处理中的任务总数" + }, + "pending": { + "type": "integer", + "title": "Pending", + "description": "待处理任务数" + }, + "voice_processing": { + "type": "integer", + "title": "Voice Processing", + "description": "音色生成中任务数" + }, + "element_processing": { + "type": "integer", + "title": "Element Processing", + "description": "主体生成中任务数" + }, + "stuck_tasks": { + "type": "integer", + "title": "Stuck Tasks", + "description": "卡住任务数(超过30分钟)" + }, + "recent_failures": { + "type": "integer", + "title": "Recent Failures", + "description": "最近1小时失败数" + } + }, + "type": "object", + "required": [ + "total_processing", + "pending", + "voice_processing", + "element_processing", + "stuck_tasks", + "recent_failures" + ], + "title": "AvatarHealthResponse", + "description": "形象克隆服务健康状态" + }, + "AvatarItem": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "形象唯一标识" + }, + "name": { + "type": "string", + "title": "Name", + "description": "展示名称" + }, + "voice_id": { + "type": "string", + "title": "Voice Id", + "description": "Kling 自定义音色 ID" + }, + "human_id": { + "type": "integer", + "title": "Human Id", + "description": "数字人主体 ID" + }, + "video_url": { + "type": "string", + "title": "Video Url", + "description": "原始人物视频 URL" + }, + "trial_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Trial Url", + "description": "音色试听 URL" + }, + "record_time": { + "type": "string", + "title": "Record Time", + "description": "创建时间 ISO 字符串" + } + }, + "type": "object", + "required": [ + "id", + "name", + "voice_id", + "human_id", + "video_url", + "record_time" + ], + "title": "AvatarItem", + "description": "形象库列表项" + }, + "AvatarTaskStatusResponse": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id" + }, + "status": { + "type": "string", + "title": "Status", + "description": "当前状态" + }, + "fail_reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Fail Reason", + "description": "失败原因" + }, + "voice_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Voice Id", + "description": "已生成的音色 ID" + }, + "human_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Human Id", + "description": "已生成的主体 ID" + }, + "trial_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Trial Url", + "description": "试听 URL" + }, + "video_url": { + "type": "string", + "title": "Video Url", + "description": "原始视频 URL" + }, + "name": { + "type": "string", + "title": "Name", + "description": "形象名称" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At", + "description": "创建时间" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At", + "description": "更新时间" + } + }, + "type": "object", + "required": [ + "task_id", + "status", + "video_url", + "name", + "created_at", + "updated_at" + ], + "title": "AvatarTaskStatusResponse", + "description": "任务状态查询响应" + }, + "Body_upload_audio_api_v1_qiniu_upload_audio_post": { + "properties": { + "file": { + "type": "string", + "contentMediaType": "application/octet-stream", + "title": "File", + "description": "音频文件(MP3, WAV, M4A, AAC, OGG)" + }, + "userId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Userid", + "description": "用户ID(可选,用于目录隔离)" + } + }, + "type": "object", + "required": [ + "file" + ], + "title": "Body_upload_audio_api_v1_qiniu_upload_audio_post" + }, + "Body_upload_avatar_api_v1_qiniu_upload_avatar_post": { + "properties": { + "file": { + "type": "string", + "contentMediaType": "application/octet-stream", + "title": "File", + "description": "形象克隆视频(MP4, MOV)" + }, + "userId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Userid", + "description": "用户ID(可选,用于目录隔离)" + }, + "fileHash": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Filehash", + "description": "前端计算的文件SHA256哈希,用于重复检测" + } + }, + "type": "object", + "required": [ + "file" + ], + "title": "Body_upload_avatar_api_v1_qiniu_upload_avatar_post" + }, + "Body_upload_video_api_v1_qiniu_upload_video_post": { + "properties": { + "file": { + "type": "string", + "contentMediaType": "application/octet-stream", + "title": "File", + "description": "视频文件(MP4, MOV, AVI, WebM)" + }, + "userId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Userid", + "description": "用户ID(可选,用于目录隔离)" + } + }, + "type": "object", + "required": [ + "file" + ], + "title": "Body_upload_video_api_v1_qiniu_upload_video_post" + }, + "Body_upload_video_api_v1_video_upload_post": { + "properties": { + "file": { + "type": "string", + "contentMediaType": "application/octet-stream", + "title": "File", + "description": "视频文件" + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "数字人名称" + } + }, + "type": "object", + "required": [ + "file" + ], + "title": "Body_upload_video_api_v1_video_upload_post" + }, + "CaptionResult-Input": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码: 0=成功, 2000=处理中" + }, + "message": { + "type": "string", + "title": "Message", + "description": "状态信息" + }, + "duration": { + "type": "number", + "title": "Duration", + "description": "音频时长(秒)" + }, + "utterances": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/CaptionUtterance" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Utterances", + "description": "字幕时间轴列表" + } + }, + "type": "object", + "required": [ + "code", + "message", + "duration" + ], + "title": "CaptionResult", + "description": "字幕生成结果" + }, + "CaptionResult-Output": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码: 0=成功, 2000=处理中" + }, + "message": { + "type": "string", + "title": "Message", + "description": "状态信息" + }, + "duration": { + "type": "number", + "title": "Duration", + "description": "音频时长(秒)" + }, + "utterances": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/CaptionUtterance" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Utterances", + "description": "字幕时间轴列表" + } + }, + "type": "object", + "required": [ + "code", + "message", + "duration" + ], + "title": "CaptionResult", + "description": "字幕生成结果" + }, + "CaptionSubmitRequest": { + "properties": { + "audio_url": { + "type": "string", + "title": "Audio Url", + "description": "音频/视频文件URL" + }, + "language": { + "type": "string", + "title": "Language", + "description": "语言: zh-CN, en-US, ja-JP, ko-KR, es-MX, ru-RU, fr-FR, yue, wuu, nan, ug", + "default": "zh-CN" + }, + "caption_type": { + "type": "string", + "title": "Caption Type", + "description": "识别类型: auto(自动), speech(说话), singing(歌词)", + "default": "auto" + }, + "use_punc": { + "type": "boolean", + "title": "Use Punc", + "description": "自动标点: True/False", + "default": true + }, + "use_itn": { + "type": "boolean", + "title": "Use Itn", + "description": "数字转换: True(中文数字转阿拉伯数字)", + "default": true + }, + "words_per_line": { + "type": "integer", + "maximum": 100.0, + "minimum": 1.0, + "title": "Words Per Line", + "description": "每行字数", + "default": 46 + }, + "max_lines": { + "type": "integer", + "maximum": 5.0, + "minimum": 1.0, + "title": "Max Lines", + "description": "每屏行数", + "default": 1 + } + }, + "type": "object", + "required": [ + "audio_url" + ], + "title": "CaptionSubmitRequest", + "description": "字幕生成任务提交请求" + }, + "CaptionTaskResponse": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id", + "description": "任务ID" + }, + "status": { + "type": "string", + "title": "Status", + "description": "任务状态: pending/processing/completed/failed" + } + }, + "type": "object", + "required": [ + "task_id", + "status" + ], + "title": "CaptionTaskResponse", + "description": "字幕任务提交响应" + }, + "CaptionUtterance": { + "properties": { + "text": { + "type": "string", + "title": "Text", + "description": "文本内容" + }, + "start_time": { + "type": "integer", + "title": "Start Time", + "description": "开始时间(毫秒)" + }, + "end_time": { + "type": "integer", + "title": "End Time", + "description": "结束时间(毫秒)" + }, + "words": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/CaptionWord" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Words", + "description": "字词级时间轴" + } + }, + "type": "object", + "required": [ + "text", + "start_time", + "end_time" + ], + "title": "CaptionUtterance", + "description": "一句话/一段字幕的时间轴信息" + }, + "CaptionWord": { + "properties": { + "text": { + "type": "string", + "title": "Text", + "description": "字/词内容" + }, + "start_time": { + "type": "integer", + "title": "Start Time", + "description": "开始时间(毫秒)" + }, + "end_time": { + "type": "integer", + "title": "End Time", + "description": "结束时间(毫秒)" + } + }, + "type": "object", + "required": [ + "text", + "start_time", + "end_time" + ], + "title": "CaptionWord", + "description": "单个字/词的时间轴信息" + }, + "CloneAvatarRequest": { + "properties": { + "name": { + "type": "string", + "maxLength": 20, + "minLength": 1, + "title": "Name", + "description": "形象名称" + }, + "video_url": { + "type": "string", + "title": "Video Url", + "description": "人物视频 URL" + } + }, + "type": "object", + "required": [ + "name", + "video_url" + ], + "title": "CloneAvatarRequest", + "description": "创建形象克隆请求" + }, + "CloneAvatarResponse": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id", + "description": "任务 ID(用于 SSE/轮询跟踪进度)" + }, + "status": { + "type": "string", + "title": "Status", + "description": "初始状态", + "default": "pending" + } + }, + "type": "object", + "required": [ + "task_id" + ], + "title": "CloneAvatarResponse", + "description": "创建形象克隆响应" + }, + "CreateCustomVoiceRequest": { + "properties": { + "voice_name": { + "type": "string", + "title": "Voice Name", + "description": "音色名称(最多20字符)", + "example": "我的音色" + }, + "audio_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Audio Url", + "description": "音频文件URL(mp3/wav/mp4/mov)" + }, + "video_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Url", + "description": "视频文件URL" + }, + "video_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Id", + "description": "历史作品ID(v2.6/sound=on/数字人/对口型生成的视频)" + }, + "callback_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Callback Url", + "description": "回调通知地址" + }, + "external_task_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "External Task Id", + "description": "自定义任务ID" + } + }, + "type": "object", + "required": [ + "voice_name" + ], + "title": "CreateCustomVoiceRequest", + "description": "创建自定义音色请求" + }, + "CreateCustomVoiceResponse": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id" + }, + "task_status": { + "type": "string", + "title": "Task Status" + }, + "created_at": { + "type": "integer", + "title": "Created At" + }, + "updated_at": { + "type": "integer", + "title": "Updated At" + } + }, + "type": "object", + "required": [ + "task_id", + "task_status", + "created_at", + "updated_at" + ], + "title": "CreateCustomVoiceResponse", + "description": "创建自定义音色响应" + }, + "CreateElementRequest": { + "properties": { + "element_name": { + "type": "string", + "title": "Element Name", + "description": "主体名称(最多20字符)", + "example": "我的小猫" + }, + "element_description": { + "type": "string", + "title": "Element Description", + "description": "主体描述(最多100字符)", + "example": "一只橘色的小猫,毛茸茸的" + }, + "reference_type": { + "type": "string", + "title": "Reference Type", + "description": "参考类型: image_refer 或 video_refer", + "default": "image_refer" + }, + "element_image_list": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/ElementImage" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Element Image List", + "description": "图片参考列表(图片定制时必填,第一个作为正面图)" + }, + "element_video_list": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/ElementVideo" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Element Video List", + "description": "视频参考列表(视频定制时必填,第一个作为正面视频)" + }, + "element_voice_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Element Voice Id", + "description": "音色ID,绑定音色到主体" + }, + "callback_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Callback Url", + "description": "回调通知地址" + } + }, + "type": "object", + "required": [ + "element_name", + "element_description" + ], + "title": "CreateElementRequest", + "description": "创建主体请求" + }, + "DigitalHuman": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "desc": { + "type": "string", + "title": "Desc" + }, + "avatar_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Avatar Url" + }, + "type": { + "type": "string", + "title": "Type", + "default": "preset" + } + }, + "type": "object", + "required": [ + "id", + "name", + "desc" + ], + "title": "DigitalHuman", + "description": "数字人信息" + }, + "ElementImage": { + "properties": { + "image_url": { + "type": "string", + "title": "Image Url", + "description": "图片URL" + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "图片名称" + } + }, + "type": "object", + "required": [ + "image_url" + ], + "title": "ElementImage", + "description": "主体参考图片(对应 KlingAI 官方格式 imageUrl)" + }, + "ElementResponse": { + "properties": { + "element_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Element Id" + }, + "element_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Element Name" + }, + "element_description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Element Description" + }, + "element_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Element Type" + }, + "status": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Status" + }, + "task_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Task Id" + }, + "task_status": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Task Status" + }, + "created_at": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Created At" + }, + "updated_at": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Updated At" + }, + "element_image_list": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Element Image List" + }, + "element_video_list": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Element Video List" + }, + "element_voice_info": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Element Voice Info" + }, + "owned_by": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Owned By" + } + }, + "type": "object", + "title": "ElementResponse", + "description": "主体响应" + }, + "ElementVideo": { + "properties": { + "video_url": { + "type": "string", + "title": "Video Url", + "description": "视频URL" + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "视频名称" + } + }, + "type": "object", + "required": [ + "video_url" + ], + "title": "ElementVideo", + "description": "主体参考视频(对应 KlingAI 官方格式 videoUrl)" + }, + "FaceChooseItem": { + "properties": { + "face_id": { + "type": "string", + "title": "Face Id", + "description": "人脸ID,由 identify-face 接口返回" + }, + "audio_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Audio Id", + "description": "通过TTS生成的音频ID(与 sound_file 二选一)" + }, + "sound_file": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Sound File", + "description": "音频文件URL或Base64(与 audio_id 二选一)" + }, + "sound_start_time": { + "type": "integer", + "title": "Sound Start Time", + "description": "音频裁剪起点时间(ms)", + "default": 0 + }, + "sound_end_time": { + "type": "integer", + "title": "Sound End Time", + "description": "音频裁剪终点时间(ms)" + }, + "sound_insert_time": { + "type": "integer", + "title": "Sound Insert Time", + "description": "裁剪后音频插入时间(ms)", + "default": 0 + } + }, + "type": "object", + "required": [ + "face_id", + "sound_end_time" + ], + "title": "FaceChooseItem", + "description": "新版对口型人脸配置" + }, + "FileUploadResponse": { + "properties": { + "key": { + "type": "string", + "title": "Key" + }, + "url": { + "type": "string", + "title": "Url" + }, + "hash": { + "type": "string", + "title": "Hash" + }, + "mimeType": { + "type": "string", + "title": "Mimetype" + }, + "fsize": { + "type": "integer", + "title": "Fsize" + }, + "isDuplicate": { + "type": "boolean", + "title": "Isduplicate", + "default": false + }, + "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Message" + }, + "existingTaskId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Existingtaskid" + } + }, + "type": "object", + "required": [ + "key", + "url", + "hash", + "mimeType", + "fsize" + ], + "title": "FileUploadResponse", + "description": "文件上传响应" + }, + "GenerateRequest": { + "properties": { + "prompt": { + "type": "string", + "title": "Prompt", + "description": "提示词" + }, + "model_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model Id", + "description": "指定模型 ID" + }, + "task_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Task Type", + "description": "任务类型,用于自动选模型: script/polish" + }, + "temperature": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Temperature", + "description": "随机性 (0-2)" + }, + "max_tokens": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Max Tokens", + "description": "最大生成长度" + } + }, + "type": "object", + "required": [ + "prompt" + ], + "title": "GenerateRequest", + "description": "生成请求" + }, + "GenerateResponse": { + "properties": { + "content": { + "type": "string", + "title": "Content" + }, + "model": { + "type": "string", + "title": "Model" + }, + "usage": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Usage" + } + }, + "type": "object", + "required": [ + "content", + "model", + "usage" + ], + "title": "GenerateResponse", + "description": "生成响应" + }, + "GenerateScriptRequest": { + "properties": { + "topic": { + "type": "string", + "maxLength": 1000, + "minLength": 1, + "title": "Topic", + "description": "创作主题/灵感" + }, + "duration": { + "type": "integer", + "maximum": 180.0, + "minimum": 30.0, + "title": "Duration", + "description": "视频时长(秒)", + "default": 45 + }, + "script_type": { + "type": "string", + "title": "Script Type", + "description": "脚本类型", + "default": "干货型" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model", + "description": "指定模型(可选)" + } + }, + "type": "object", + "required": [ + "topic" + ], + "title": "GenerateScriptRequest", + "description": "生成脚本请求" + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "HealthResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status" + }, + "total_models": { + "type": "integer", + "title": "Total Models" + }, + "available_models": { + "type": "integer", + "title": "Available Models" + }, + "models": { + "items": { + "type": "object" + }, + "type": "array", + "title": "Models" + } + }, + "type": "object", + "required": [ + "status", + "total_models", + "available_models", + "models" + ], + "title": "HealthResponse", + "description": "健康检查响应" + }, + "IdentifyFaceRequest": { + "properties": { + "video_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Id", + "description": "KlingAI 生成的视频 ID" + }, + "video_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Url", + "description": "上传的视频 URL(与 videoId 二选一" + } + }, + "type": "object", + "title": "IdentifyFaceRequest", + "description": "人脸识别请求" + }, + "IdentifyFaceResponse": { + "properties": { + "session_id": { + "type": "string", + "title": "Session Id" + }, + "face_data": { + "items": { + "type": "object" + }, + "type": "array", + "title": "Face Data" + } + }, + "type": "object", + "required": [ + "session_id", + "face_data" + ], + "title": "IdentifyFaceResponse", + "description": "人脸识别响应" + }, + "Image2VideoRequest": { + "properties": { + "image_url": { + "type": "string", + "title": "Image Url", + "description": "输入图片 URL" + }, + "prompt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Prompt", + "description": "视频运动描述提示词" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model", + "description": "视频模型", + "default": "kling-v2.6" + }, + "duration": { + "type": "integer", + "maximum": 10.0, + "minimum": 5.0, + "title": "Duration", + "description": "视频时长(秒)", + "default": 5 + }, + "aspect_ratio": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Aspect Ratio", + "description": "宽高比" + }, + "mode": { + "type": "string", + "title": "Mode", + "description": "生成模式", + "default": "pro" + }, + "callback_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Callback Url", + "description": "回调通知地址" + } + }, + "type": "object", + "required": [ + "image_url" + ], + "title": "Image2VideoRequest", + "description": "图生视频请求" + }, + "ImageGenerateRequest": { + "properties": { + "prompt": { + "type": "string", + "title": "Prompt", + "description": "图像描述提示词" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model", + "description": "图像模型: kolors-v1", + "default": "kolors-v1" + }, + "width": { + "type": "integer", + "title": "Width", + "description": "图像宽度", + "default": 1024 + }, + "height": { + "type": "integer", + "title": "Height", + "description": "图像高度", + "default": 1024 + }, + "negative_prompt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Negative Prompt", + "description": "负面提示词" + }, + "callback_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Callback Url", + "description": "回调通知地址" + } + }, + "type": "object", + "required": [ + "prompt" + ], + "title": "ImageGenerateRequest", + "description": "图像生成请求" + }, + "LoginResponse": { + "properties": { + "token": { + "type": "string", + "title": "Token", + "description": "JWT 访问令牌" + }, + "user": { + "$ref": "#/components/schemas/UserInfo" + } + }, + "type": "object", + "required": [ + "token", + "user" + ], + "title": "LoginResponse", + "description": "登录响应" + }, + "MobileLoginRequest": { + "properties": { + "mobile": { + "type": "string", + "maxLength": 20, + "minLength": 11, + "title": "Mobile", + "description": "手机号" + }, + "nickname": { + "anyOf": [ + { + "type": "string", + "maxLength": 64 + }, + { + "type": "null" + } + ], + "title": "Nickname", + "description": "用户昵称" + } + }, + "type": "object", + "required": [ + "mobile" + ], + "title": "MobileLoginRequest", + "description": "手机号登录请求" + }, + "ModelHealthInfo": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "模型 ID" + }, + "name": { + "type": "string", + "title": "Name", + "description": "模型名称" + }, + "is_available": { + "type": "boolean", + "title": "Is Available", + "description": "是否可用" + }, + "response_time": { + "type": "number", + "title": "Response Time", + "description": "响应时间(毫秒)" + }, + "last_error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Last Error", + "description": "上次错误信息" + } + }, + "type": "object", + "required": [ + "id", + "name", + "is_available", + "response_time" + ], + "title": "ModelHealthInfo", + "description": "模型健康信息" + }, + "ModelHealthResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status", + "description": "整体状态:healthy / unhealthy / error" + }, + "models": { + "items": { + "$ref": "#/components/schemas/ModelHealthInfo" + }, + "type": "array", + "title": "Models", + "description": "各模型状态" + }, + "recommended_model": { + "anyOf": [ + { + "$ref": "#/components/schemas/ModelHealthInfo" + }, + { + "type": "null" + } + ], + "description": "推荐的模型" + }, + "total_models": { + "type": "integer", + "title": "Total Models", + "description": "模型总数" + }, + "available_models": { + "type": "integer", + "title": "Available Models", + "description": "可用模型数" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error", + "description": "错误信息" + } + }, + "type": "object", + "required": [ + "status", + "models", + "total_models", + "available_models" + ], + "title": "ModelHealthResponse", + "description": "模型健康检查响应" + }, + "ModelResponse": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "platform_id": { + "type": "string", + "title": "Platform Id" + }, + "model_name": { + "type": "string", + "title": "Model Name" + }, + "display_name": { + "type": "string", + "title": "Display Name" + }, + "capabilities": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Capabilities" + }, + "default_params": { + "type": "object", + "title": "Default Params" + }, + "is_enabled": { + "type": "boolean", + "title": "Is Enabled" + }, + "full_model_id": { + "type": "string", + "title": "Full Model Id" + } + }, + "type": "object", + "required": [ + "id", + "platform_id", + "model_name", + "display_name", + "capabilities", + "default_params", + "is_enabled", + "full_model_id" + ], + "title": "ModelResponse", + "description": "模型响应" + }, + "OmniImageRequest": { + "properties": { + "prompt": { + "type": "string", + "title": "Prompt", + "description": "图像描述提示词" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model", + "description": "模型: kling-image-o1, kling-v3-omni", + "default": "kling-image-o1" + }, + "aspect_ratio": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Aspect Ratio", + "description": "宽高比: 16:9/9:16/1:1/4:3/3:4", + "default": "9:16" + }, + "resolution": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Resolution", + "description": "清晰度: 1k/2k/4k", + "default": "1k" + }, + "result_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Result Type", + "description": "结果类型: single/series", + "default": "single" + }, + "n": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "N", + "description": "生成数量 1-9", + "default": 1 + }, + "element_list": { + "anyOf": [ + { + "items": { + "type": "object" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Element List", + "description": "主体参考列表" + }, + "image_list": { + "anyOf": [ + { + "items": { + "type": "object" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Image List", + "description": "参考图列表" + }, + "callback_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Callback Url", + "description": "回调通知地址" + } + }, + "type": "object", + "required": [ + "prompt" + ], + "title": "OmniImageRequest", + "description": "Omni-Image 图像生成请求" + }, + "OmniVideoRequest": { + "properties": { + "prompt": { + "type": "string", + "title": "Prompt", + "description": "视频描述提示词(不超过2500字符),支持 <<>>/<<>>/<<>> 引用语法", + "example": "一只<<>>在花园里奔跑,阳光明媚" + }, + "model": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model", + "description": "模型: kling-v3-omni, kling-video-o1", + "default": "kling-v3-omni" + }, + "duration": { + "type": "integer", + "maximum": 15.0, + "minimum": 3.0, + "title": "Duration", + "description": "视频时长(秒): 3/5/10/15,Omni支持3-15秒", + "default": 5 + }, + "aspect_ratio": { + "type": "string", + "title": "Aspect Ratio", + "description": "宽高比: 16:9, 9:16, 1:1", + "default": "16:9" + }, + "mode": { + "type": "string", + "title": "Mode", + "description": "生成模式: pro(高质量) 或 std(标准)", + "default": "pro" + }, + "sound": { + "type": "string", + "title": "Sound", + "description": "声音控制: on=音画同出, off=无声", + "default": "on" + }, + "negative_prompt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Negative Prompt", + "description": "负面提示词" + }, + "multi_shot": { + "type": "boolean", + "title": "Multi Shot", + "description": "是否启用多镜头模式", + "default": false + }, + "shot_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Shot Type", + "description": "分镜方式: customize=自定义, intelligence=智能分镜" + }, + "multi_prompt": { + "items": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ] + }, + "type": "array", + "title": "Multi Prompt", + "description": "多镜头提示词列表,每个元素包含 index, prompt, duration,最多6个分镜" + }, + "image_list": { + "items": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ] + }, + "type": "array", + "title": "Image List", + "description": "参考图片列表,最多4张" + }, + "element_list": { + "items": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ] + }, + "type": "array", + "title": "Element List", + "description": "主体参考列表,格式: [{'elementId': 123}], 最多7个" + }, + "video_list": { + "items": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ] + }, + "type": "array", + "title": "Video List", + "description": "参考视频列表" + }, + "callback_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Callback Url", + "description": "回调通知地址" + }, + "external_task_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "External Task Id", + "description": "自定义任务ID" + } + }, + "type": "object", + "required": [ + "prompt" + ], + "title": "OmniVideoRequest", + "description": "Omni-Video 视频生成请求(kling-v3-omni / kling-video-o1)" + }, + "PlatformResponse": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "provider": { + "type": "string", + "title": "Provider" + } + }, + "type": "object", + "required": [ + "id", + "name", + "provider" + ], + "title": "PlatformResponse", + "description": "平台响应" + }, + "PolishResponse": { + "properties": { + "success": { + "type": "boolean", + "title": "Success" + }, + "original": { + "type": "string", + "title": "Original" + }, + "polished": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Polished" + }, + "polish_type": { + "type": "string", + "title": "Polish Type" + }, + "model": { + "type": "string", + "title": "Model" + }, + "usage": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Usage" + } + }, + "type": "object", + "required": [ + "success", + "original", + "polished", + "polish_type", + "model", + "usage" + ], + "title": "PolishResponse", + "description": "润色响应" + }, + "PromptTemplatesResponse": { + "properties": { + "script_types": { + "items": { + "type": "object" + }, + "type": "array", + "title": "Script Types" + }, + "video_styles": { + "items": { + "type": "object" + }, + "type": "array", + "title": "Video Styles" + }, + "tones": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Tones" + } + }, + "type": "object", + "required": [ + "script_types", + "video_styles", + "tones" + ], + "title": "PromptTemplatesResponse", + "description": "Prompt 模板配置响应" + }, + "ScriptGenerateRequest": { + "properties": { + "topic": { + "type": "string", + "title": "Topic", + "description": "脚本主题", + "example": "水电改造的3个致命错误" + }, + "duration": { + "type": "integer", + "maximum": 120.0, + "minimum": 15.0, + "title": "Duration", + "description": "视频时长(秒)", + "default": 30 + }, + "script_type": { + "type": "string", + "title": "Script Type", + "description": "脚本类型", + "default": "干货型" + }, + "video_style": { + "type": "string", + "title": "Video Style", + "description": "视频风格", + "default": "口播" + }, + "tone": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Tone", + "description": "语气风格" + }, + "requirements": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Requirements", + "description": "额外要求" + }, + "model_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model Id", + "description": "指定模型ID,默认使用系统默认模型" + } + }, + "type": "object", + "required": [ + "topic" + ], + "title": "ScriptGenerateRequest", + "description": "脚本生成请求" + }, + "ScriptGenerateResponse": { + "properties": { + "success": { + "type": "boolean", + "title": "Success" + }, + "script": { + "items": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ] + }, + "type": "array", + "title": "Script" + }, + "total_duration": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Total Duration" + }, + "target_duration": { + "type": "integer", + "title": "Target Duration" + }, + "total_word_count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Total Word Count" + }, + "segment_count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Segment Count" + }, + "empty_shot_count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Empty Shot Count" + }, + "script_type": { + "type": "string", + "title": "Script Type" + }, + "model": { + "type": "string", + "title": "Model" + }, + "usage": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Usage" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error" + }, + "raw_content": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Raw Content" + } + }, + "type": "object", + "required": [ + "success", + "script", + "total_duration", + "target_duration", + "total_word_count", + "segment_count", + "empty_shot_count", + "script_type", + "model", + "usage", + "error", + "raw_content" + ], + "title": "ScriptGenerateResponse", + "description": "脚本生成响应 - 针对前端展示优化" + }, + "Segment": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "分镜ID" + }, + "type": { + "type": "string", + "enum": [ + "segment", + "empty_shot" + ], + "title": "Type", + "description": "分镜类型: segment(分镜) 或 empty_shot(空镜)", + "default": "segment" + }, + "scene": { + "type": "string", + "title": "Scene", + "description": "场景描述/画面描述", + "default": "" + }, + "voiceover": { + "type": "string", + "title": "Voiceover", + "description": "配音文案(空镜可为空)", + "default": "" + }, + "duration": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Duration", + "description": "时长(秒)" + }, + "human_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Human Id", + "description": "数字人主体ID" + }, + "voice_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Voice Id", + "description": "音色ID(空镜时使用)" + }, + "status": { + "$ref": "#/components/schemas/SegmentStatus", + "default": "pending" + }, + "provider_task_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Provider Task Id", + "description": "供应商任务ID(如 Kling task_id)" + }, + "video_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Url", + "description": "生成后的视频URL" + }, + "local_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Local Path", + "description": "本地视频路径" + }, + "qiniu_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Qiniu Url", + "description": "七牛云URL" + }, + "error_message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error Message", + "description": "错误信息" + }, + "stage": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Stage", + "description": "内部处理阶段(如 image_generating / video_processing)" + }, + "image_task_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Image Task Id", + "description": "空镜文生图任务ID(内部使用)" + }, + "query_fail_count": { + "type": "integer", + "title": "Query Fail Count", + "description": "查询失败计数", + "default": 0 + } + }, + "type": "object", + "required": [ + "id" + ], + "title": "Segment", + "description": "视频分镜/镜头定义\n\n术语说明:\n- segment: 分镜(带数字人)\n- empty_shot: 空镜(无数字人)" + }, + "SegmentStatus": { + "type": "string", + "enum": [ + "pending", + "submitted", + "processing", + "completed", + "failed" + ], + "title": "SegmentStatus", + "description": "视频分镜处理状态" + }, + "ShotResult": { + "properties": { + "segment_id": { + "type": "string", + "title": "Segment Id" + }, + "type": { + "type": "string", + "title": "Type" + }, + "status": { + "type": "string", + "title": "Status" + }, + "task_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Task Id" + }, + "video_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Url" + }, + "local_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Local Path" + }, + "error_message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error Message" + } + }, + "type": "object", + "required": [ + "segment_id", + "type", + "status" + ], + "title": "ShotResult", + "description": "单个分镜结果" + }, + "SrtSubtitleResponse": { + "properties": { + "srt_content": { + "type": "string", + "title": "Srt Content", + "description": "SRT 格式字幕内容" + }, + "utterances": { + "items": { + "$ref": "#/components/schemas/CaptionUtterance" + }, + "type": "array", + "title": "Utterances", + "description": "原始时间轴数据" + } + }, + "type": "object", + "required": [ + "srt_content", + "utterances" + ], + "title": "SrtSubtitleResponse", + "description": "SRT 字幕格式响应" + }, + "TaskCreateRequest": { + "properties": { + "project_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Project Id", + "description": "项目ID(可选)" + }, + "params": { + "type": "object", + "title": "Params", + "description": "任务参数" + } + }, + "type": "object", + "title": "TaskCreateRequest", + "description": "创建任务请求" + }, + "TaskCreateResponse": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id", + "description": "任务ID" + }, + "status": { + "type": "string", + "title": "Status", + "description": "任务状态", + "default": "pending" + }, + "message": { + "type": "string", + "title": "Message", + "description": "状态消息", + "default": "任务已创建" + } + }, + "type": "object", + "required": [ + "task_id" + ], + "title": "TaskCreateResponse", + "description": "创建任务响应" + }, + "TestModelRequest": { + "properties": { + "model_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model Id", + "description": "要测试的模型 ID" + } + }, + "type": "object", + "title": "TestModelRequest", + "description": "测试模型请求" + }, + "TestModelResponse": { + "properties": { + "success": { + "type": "boolean", + "title": "Success", + "description": "是否成功" + }, + "model": { + "type": "string", + "title": "Model", + "description": "模型名称" + }, + "response_time": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Response Time", + "description": "响应时间(毫秒)" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error", + "description": "错误信息" + }, + "checked_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Checked At", + "description": "检查时间 ISO 格式" + } + }, + "type": "object", + "required": [ + "success", + "model" + ], + "title": "TestModelResponse", + "description": "测试模型响应" + }, + "UpdateAvatarNameRequest": { + "properties": { + "name": { + "type": "string", + "maxLength": 20, + "minLength": 1, + "title": "Name", + "description": "新形象名称" + } + }, + "type": "object", + "required": [ + "name" + ], + "title": "UpdateAvatarNameRequest", + "description": "更新形象名称请求" + }, + "UploadTokenRequest": { + "properties": { + "key": { + "type": "string", + "title": "Key", + "description": "文件存储 Key" + }, + "expires": { + "type": "integer", + "title": "Expires", + "description": "Token 有效期(秒)", + "default": 3600 + } + }, + "type": "object", + "required": [ + "key" + ], + "title": "UploadTokenRequest", + "description": "上传凭证请求" + }, + "UploadTokenResponse": { + "properties": { + "token": { + "type": "string", + "title": "Token" + }, + "key": { + "type": "string", + "title": "Key" + }, + "uploadUrl": { + "type": "string", + "title": "Uploadurl", + "default": "https://upload.qiniup.com" + } + }, + "type": "object", + "required": [ + "token", + "key" + ], + "title": "UploadTokenResponse", + "description": "上传凭证响应" + }, + "UserInfo": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "用户 ID" + }, + "nickname": { + "type": "string", + "title": "Nickname", + "description": "用户昵称" + }, + "avatar": { + "type": "string", + "title": "Avatar", + "description": "头像 URL", + "default": "" + } + }, + "type": "object", + "required": [ + "id", + "nickname" + ], + "title": "UserInfo", + "description": "用户信息" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + }, + "input": { + "title": "Input" + }, + "ctx": { + "type": "object", + "title": "Context" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + }, + "VideoGenerateRequest": { + "properties": { + "project_id": { + "type": "string", + "title": "Project Id", + "description": "项目ID" + }, + "human_id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Human Id", + "description": "数字人主体ID(分镜类型使用)" + }, + "segments": { + "items": { + "$ref": "#/components/schemas/Segment" + }, + "type": "array", + "title": "Segments", + "description": "分镜列表" + } + }, + "type": "object", + "required": [ + "project_id", + "segments" + ], + "title": "VideoGenerateRequest", + "description": "视频生成请求" + }, + "VideoJobDetail": { + "properties": { + "job_id": { + "type": "string", + "title": "Job Id" + }, + "project_id": { + "type": "string", + "title": "Project Id" + }, + "status": { + "type": "string", + "title": "Status" + }, + "progress": { + "type": "integer", + "title": "Progress" + }, + "total_segments": { + "type": "integer", + "title": "Total Segments" + }, + "completed_segments": { + "type": "integer", + "title": "Completed Segments" + }, + "failed_segments": { + "type": "integer", + "title": "Failed Segments" + }, + "segments": { + "items": { + "$ref": "#/components/schemas/ShotResult" + }, + "type": "array", + "title": "Segments" + }, + "created_at": { + "type": "number", + "title": "Created At" + }, + "updated_at": { + "type": "number", + "title": "Updated At" + } + }, + "type": "object", + "required": [ + "job_id", + "project_id", + "status", + "progress", + "total_segments", + "completed_segments", + "failed_segments", + "segments", + "created_at", + "updated_at" + ], + "title": "VideoJobDetail", + "description": "视频作业详情" + }, + "VideoJobStatus": { + "properties": { + "job_id": { + "type": "string", + "title": "Job Id" + }, + "project_id": { + "type": "string", + "title": "Project Id" + }, + "status": { + "type": "string", + "title": "Status" + }, + "progress": { + "type": "integer", + "title": "Progress" + }, + "total_segments": { + "type": "integer", + "title": "Total Segments" + }, + "completed_segments": { + "type": "integer", + "title": "Completed Segments" + }, + "failed_segments": { + "type": "integer", + "title": "Failed Segments" + }, + "created_at": { + "type": "number", + "title": "Created At" + }, + "updated_at": { + "type": "number", + "title": "Updated At" + }, + "error_message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error Message" + } + }, + "type": "object", + "required": [ + "job_id", + "project_id", + "status", + "progress", + "total_segments", + "completed_segments", + "failed_segments", + "created_at", + "updated_at" + ], + "title": "VideoJobStatus", + "description": "视频作业状态" + }, + "VirtualTryonRequest": { + "properties": { + "person_image_url": { + "type": "string", + "title": "Person Image Url", + "description": "人物图片 URL" + }, + "cloth_image_url": { + "type": "string", + "title": "Cloth Image Url", + "description": "衣服图片 URL" + }, + "callback_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Callback Url", + "description": "回调通知地址" + } + }, + "type": "object", + "required": [ + "person_image_url", + "cloth_image_url" + ], + "title": "VirtualTryonRequest", + "description": "虚拟试穿请求" + }, + "VoiceInfo": { + "properties": { + "voice_id": { + "type": "string", + "title": "Voice Id" + }, + "voice_name": { + "type": "string", + "title": "Voice Name" + }, + "trial_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Trial Url" + }, + "owned_by": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Owned By" + }, + "status": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Status" + } + }, + "type": "object", + "required": [ + "voice_id", + "voice_name" + ], + "title": "VoiceInfo", + "description": "音色信息" + }, + "app__api__v1__ai_models__PolishRequest": { + "properties": { + "content": { + "type": "string", + "title": "Content", + "description": "需要润色的内容" + }, + "polish_type": { + "type": "string", + "title": "Polish Type", + "description": "润色类型:scene/voiceover", + "default": "voiceover" + }, + "model_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model Id", + "description": "指定模型ID" + } + }, + "type": "object", + "required": [ + "content" + ], + "title": "PolishRequest", + "description": "润色请求" + }, + "app__api__v1__klingai__TaskStatusResponse": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id" + }, + "task_status": { + "type": "string", + "title": "Task Status" + }, + "created_at": { + "type": "integer", + "title": "Created At" + }, + "updated_at": { + "type": "integer", + "title": "Updated At" + }, + "video_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Url" + }, + "image_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Image Url" + }, + "error_message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error Message" + } + }, + "type": "object", + "required": [ + "task_id", + "task_status", + "created_at", + "updated_at" + ], + "title": "TaskStatusResponse", + "description": "任务状态响应" + }, + "app__api__v1__klingai__VideoGenerateResponse": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id" + }, + "task_status": { + "type": "string", + "title": "Task Status" + }, + "created_at": { + "type": "integer", + "title": "Created At" + }, + "updated_at": { + "type": "integer", + "title": "Updated At" + } + }, + "type": "object", + "required": [ + "task_id", + "task_status", + "created_at", + "updated_at" + ], + "title": "VideoGenerateResponse", + "description": "视频生成响应" + }, + "app__api__v1__tasks__TaskStatusResponse": { + "properties": { + "task_id": { + "type": "string", + "title": "Task Id", + "description": "任务ID" + }, + "type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Type", + "description": "任务类型" + }, + "status": { + "type": "string", + "title": "Status", + "description": "任务状态: pending/running/waiting/completed/failed" + }, + "progress": { + "type": "integer", + "title": "Progress", + "description": "进度百分比 (0-100)", + "default": 0 + }, + "message": { + "type": "string", + "title": "Message", + "description": "状态描述", + "default": "" + }, + "completed": { + "type": "integer", + "title": "Completed", + "description": "已完成子任务数", + "default": 0 + }, + "total": { + "type": "integer", + "title": "Total", + "description": "总子任务数", + "default": 0 + }, + "result": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Result", + "description": "任务结果(完成时)" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error", + "description": "错误信息(失败时)" + } + }, + "type": "object", + "required": [ + "task_id", + "status" + ], + "title": "TaskStatusResponse", + "description": "任务状态响应" + }, + "app__api__v1__video__VideoGenerateResponse": { + "properties": { + "job_id": { + "type": "string", + "title": "Job Id", + "description": "作业ID" + }, + "task_id": { + "type": "string", + "title": "Task Id", + "description": "任务ID(与job_id相同,兼容前端)" + }, + "status": { + "type": "string", + "title": "Status", + "description": "作业状态" + }, + "message": { + "type": "string", + "title": "Message", + "description": "状态消息" + }, + "sse_url": { + "type": "string", + "title": "Sse Url", + "description": "SSE进度流URL" + } + }, + "type": "object", + "required": [ + "job_id", + "task_id", + "status", + "message", + "sse_url" + ], + "title": "VideoGenerateResponse", + "description": "视频生成响应" + }, + "app__schemas__common__ApiResponse_VideoGenerateResponse___1": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/app__api__v1__klingai__VideoGenerateResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[VideoGenerateResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "app__schemas__common__ApiResponse_VideoGenerateResponse___2": { + "properties": { + "code": { + "type": "integer", + "title": "Code", + "description": "状态码,200 表示成功", + "default": 200 + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/app__api__v1__video__VideoGenerateResponse" + }, + { + "type": "null" + } + ], + "description": "响应数据" + }, + "message": { + "type": "string", + "title": "Message", + "description": "提示信息", + "default": "success" + } + }, + "type": "object", + "title": "ApiResponse[VideoGenerateResponse]", + "example": { + "code": 200, + "data": {}, + "message": "success" + } + }, + "app__schemas__script__PolishRequest": { + "properties": { + "content": { + "type": "string", + "minLength": 1, + "title": "Content", + "description": "待润色内容" + }, + "polish_type": { + "type": "string", + "title": "Polish Type", + "description": "润色类型:scene / voiceover", + "default": "voiceover" + }, + "shot_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Shot Type", + "description": "镜头类型:segment(分镜) / empty_shot(空镜),用于画面润色时区分", + "default": "segment" + } + }, + "type": "object", + "required": [ + "content" + ], + "title": "PolishRequest", + "description": "润色请求" + } + }, + "securitySchemes": { + "HTTPBearer": { + "type": "http", + "scheme": "bearer" + } + } + } +} diff --git a/tauri-app/src/api/generated/schema.ts b/tauri-app/src/api/generated/schema.ts new file mode 100644 index 0000000..13d0166 --- /dev/null +++ b/tauri-app/src/api/generated/schema.ts @@ -0,0 +1,7148 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/api/v1/auth/login": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Login + * @description 手机号登录/注册 + * + * - 如果手机号已存在,返回对应用户 + * - 如果不存在,自动创建新用户 + * - 返回 JWT Token 用于后续认证 + */ + post: operations["login_api_v1_auth_login_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/auth/me": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Me + * @description 获取当前登录用户信息 + */ + get: operations["get_me_api_v1_auth_me_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/auth/refresh": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Refresh Token + * @description 刷新 JWT Token + */ + post: operations["refresh_token_api_v1_auth_refresh_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/script/generate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Generate Script + * @description 同步生成脚本 + * + * 直接返回生成的分镜列表,适合快速预览。 + */ + post: operations["generate_script_api_v1_script_generate_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/script/generate/stream": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Generate Script Stream + * @description 流式生成脚本(SSE) + * + * 返回 Server-Sent Events,包含进度更新和最终结果。 + * 前端通过 EventSource 接收实时进度。 + * + * **SSE 事件类型:** + * - `start`: 开始生成 + * - `analyzing`: 分析主题 + * - `planning`: 规划结构 + * - `generating`: AI 生成中 + * - `parsing`: 解析结果 + * - `complete`: 完成,包含 result 字段 + * - `error`: 错误 + * + * **示例事件流:** + * ``` + * data: {"type": "start", "progress": 0, "message": "开始生成脚本"} + * + * data: {"type": "analyzing", "progress": 15, "message": "分析目标受众..."} + * + * data: {"type": "complete", "progress": 100, "message": "成功生成 5 个分镜", "result": [...]} + * ``` + */ + post: operations["generate_script_stream_api_v1_script_generate_stream_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/script/polish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Polish Content + * @description AI 润色文案/画面描述 + * + * - `polishType=scene`: 润色画面描述(根据 shot_type 自动区分分镜/空镜) + * - `polishType=voiceover`: 润色配音文案 + * + * 参数: + * - `shot_type`: "segment"(分镜)或 "empty_shot"(空镜),画面润色时必填 + */ + post: operations["polish_content_api_v1_script_polish_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/script/model-health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Check Model Health + * @description 检查 AI 模型健康状态 + * + * 返回所有配置的模型及其可用性状态。 + */ + get: operations["check_model_health_api_v1_script_model_health_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/script/test-model": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Test Model + * @description 测试指定模型连接 + * + * 发送一个简单的测试请求,验证模型是否可用。 + */ + post: operations["test_model_api_v1_script_test_model_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/ai/platforms": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Platforms + * @description 获取所有平台列表 + */ + get: operations["list_platforms_api_v1_ai_platforms_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/ai/models": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Models + * @description 获取模型列表 + * + * Args: + * capability: 按能力过滤,如 script、polish、chat + */ + get: operations["list_models_api_v1_ai_models_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/ai/generate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Generate Text + * @description 文本生成(自动路由到对应平台) + */ + post: operations["generate_text_api_v1_ai_generate_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/ai/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Health Check + * @description 检查模型健康状态 + */ + get: operations["health_check_api_v1_ai_health_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/ai/platforms/{platform_id}/test": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Test Platform Connection + * @description 测试平台连接 + */ + get: operations["test_platform_connection_api_v1_ai_platforms__platform_id__test_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/ai/reload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Reload Config + * @description 重新加载配置文件 + */ + post: operations["reload_config_api_v1_ai_reload_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/ai/prompts/templates": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Prompt Templates + * @description 获取所有可用的 Prompt 模板配置 + * + * 包括脚本类型、视频风格、语气风格等选项。 + */ + get: operations["get_prompt_templates_api_v1_ai_prompts_templates_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/ai/prompts/build": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Build System Prompt + * @description 构建系统 Prompt(用于调试和预览) + * + * 返回构建好的系统 Prompt,可用于前端预览或调试。 + */ + post: operations["build_system_prompt_api_v1_ai_prompts_build_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/ai/scripts/generate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Generate Script + * @description 生成家装行业短视频脚本 + * + * 使用专业的 Prompt 模板生成包含分镜+空镜的混合脚本。 + * 针对前端展示优化,返回分镜数、空镜数、总字数等统计信息。 + */ + post: operations["generate_script_api_v1_ai_scripts_generate_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/ai/scripts/polish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Polish Script Content + * @description 润色脚本内容 + * + * 对场景描述或口播文案进行专业润色。 + */ + post: operations["polish_script_content_api_v1_ai_scripts_polish_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/videos/omni": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Omni Video Tasks + * @description 查询 Omni-Video 任务列表 + * + * 查询历史 Omni-Video 任务列表。 + */ + get: operations["list_omni_video_tasks_api_v1_klingai_videos_omni_get"]; + put?: never; + /** + * Create Omni Video + * @description Omni-Video 多模态视频生成 + * + * 支持文本、图片、主体、视频等多种输入方式组合生成视频。 + * 适用于 kling-v3-omni 和 kling-video-o1 模型。 + * + * **特性:** + * - 支持 3-15 秒视频生成 + * - 支持多镜头和智能分镜 (shotType=intelligence) + * - 支持引用主体、图片、视频作为参考 + * - 支持 <<>>/<<>>/<<>> 语法引用提示词中的资源 + */ + post: operations["create_omni_video_api_v1_klingai_videos_omni_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/videos/omni/{task_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Omni Video Task + * @description 查询 Omni-Video 任务状态 + * + * 查询指定 Omni-Video 任务的执行状态和结果。 + */ + get: operations["get_omni_video_task_api_v1_klingai_videos_omni__task_id__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/videos/image2video": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create Image To Video + * @description 图生视频 + * + * 根据输入图片生成视频。 + */ + post: operations["create_image_to_video_api_v1_klingai_videos_image2video_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/videos/extend": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Extend Video + * @description 视频延长 + * + * 延长现有视频的时长。 + */ + post: operations["extend_video_api_v1_klingai_videos_extend_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/videos/identify-face": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Identify Face + * @description 对口型前置:人脸识别 + * + * 分析视频中的人脸信息,返回 sessionId 和 faceId,用于后续的 advanced-lip-sync。 + */ + post: operations["identify_face_api_v1_klingai_videos_identify_face_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/videos/advanced-lip-sync": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create Advanced Lip Sync + * @description 新版对口型视频生成 + * + * 基于 KlingAI advanced-lip-sync 接口,先调用 /videos/identify-face 获取 sessionId 和 faceId, + * 再传入本接口生成对口型视频。 + * + * 支持 audio_id(TTS 生成)或 soundFile(外部音频 URL)驱动口型。 + */ + post: operations["create_advanced_lip_sync_api_v1_klingai_videos_advanced_lip_sync_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/videos/advanced-lip-sync/{taskId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Advanced Lip Sync Task + * @description 查询新版对口型任务状态 + */ + get: operations["get_advanced_lip_sync_task_api_v1_klingai_videos_advanced_lip_sync__taskId__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/images/omni": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create Omni Image + * @description Omni-Image 图像生成 + * + * 支持文本、主体、参考图等多种输入方式组合生成图像。 + */ + post: operations["create_omni_image_api_v1_klingai_images_omni_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/images/omni/{task_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Omni Image Task + * @description 查询 Omni-Image 任务状态 + */ + get: operations["get_omni_image_task_api_v1_klingai_images_omni__task_id__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/images/generations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create Image + * @description 文生图 + * + * 根据文本描述生成图像。 + */ + post: operations["create_image_api_v1_klingai_images_generations_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/virtual-tryon": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create Virtual Tryon + * @description 虚拟试穿 + * + * 将衣服虚拟试穿到人物身上。 + */ + post: operations["create_virtual_tryon_api_v1_klingai_virtual_tryon_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/tasks/{taskId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Taskstatus + * @description 查询任务状态 + * + * 查询指定任务的执行状态和结果。 + * + * Args: + * taskId: 任务 ID + * task_type: 任务类型 (video, image2video, image, lip-sync, virtual-tryon) + */ + get: operations["get_taskStatus_api_v1_klingai_tasks__taskId__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/tasks": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Tasks + * @description 查询任务列表 + * + * 查询历史任务列表。 + */ + get: operations["list_tasks_api_v1_klingai_tasks_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/elements": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Elements + * @description 查询主体列表 + * + * 获取所有已创建的主体(自定义元素)列表。 + */ + get: operations["list_elements_api_v1_klingai_elements_get"]; + put?: never; + /** + * Create Element + * @description 创建主体(自定义元素) + * + * 通过上传图片或视频创建可复用的主体,用于视频/图像生成时保持角色一致性。 + * + * 图片要求: + * - 格式:jpg, jpeg, png + * - 大小:≤10MB + * - 数量:正面图 + 1-3张其他角度 + * + * 视频要求: + * - 格式:mp4, mov + * - 时长:3-8秒 + * - 分辨率:1080P + * - 大小:≤200MB + */ + post: operations["create_element_api_v1_klingai_elements_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/elements/{elementId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Element + * @description 查询单个主体详情 + * + * 获取指定主体的详细信息。 + */ + get: operations["get_element_api_v1_klingai_elements__elementId__get"]; + put?: never; + post?: never; + /** + * Delete Element + * @description 删除主体 + * + * 删除不再使用的主体(自定义元素)。 + */ + delete: operations["delete_element_api_v1_klingai_elements__elementId__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/elements/ai-multi-shot": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Ai Multishot + * @description 智能补全主体不同角度图片 + * + * 通过主体正面图,自动推理出该主体其他角度图片。 + * 每次可生成3组结果供选择,每次扣减0.5积分。 + * + * 使用流程: + * 1. 调用此接口传入正面图 + * 2. 轮询查询任务状态 + * 3. 获取生成的多组角度图片 + * 4. 选择合适的图片创建主体 + */ + post: operations["ai_multiShot_api_v1_klingai_elements_ai_multi_shot_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/elements/ai-multi-shot/{taskId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Ai Multishot Task + * @description 查询智能补全主体图任务状态 + * + * 获取指定任务的执行状态和生成的多角度图片结果。 + */ + get: operations["get_ai_multiShot_task_api_v1_klingai_elements_ai_multi_shot__taskId__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/voices/custom": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Custom Voices + * @description 查询自定义音色列表 + * + * 获取所有已创建的自定义音色。 + */ + get: operations["list_custom_voices_api_v1_klingai_voices_custom_get"]; + put?: never; + /** + * Create Custom Voice + * @description 创建自定义音色 + * + * 通过上传音频文件或引用历史视频创建自定义音色,用于对口型视频。 + * + * 音频要求: + * - 格式:mp3, wav, mp4, mov + * - 时长:5-30 秒 + * - 人声干净、无杂音、单一人声 + */ + post: operations["create_custom_voice_api_v1_klingai_voices_custom_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/voices/custom/{voiceId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Custom Voice + * @description 查询单个自定义音色 + * + * 获取指定自定义音色的详细信息。 + */ + get: operations["get_custom_voice_api_v1_klingai_voices_custom__voiceId__get"]; + put?: never; + post?: never; + /** + * Delete Custom Voice + * @description 删除自定义音色 + * + * 删除不再使用的自定义音色。 + */ + delete: operations["delete_custom_voice_api_v1_klingai_voices_custom__voiceId__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/klingai/voices/presets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Preset Voices + * @description 查询官方预设音色列表 + * + * 获取 KlingAI 提供的官方音色列表。 + */ + get: operations["list_preset_voices_api_v1_klingai_voices_presets_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/qiniu/upload-token": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Get Upload Token + * @description 获取上传凭证(客户端直传) + * + * 前端获取 Token 后,可直接上传到七牛云,无需经过服务端。 + * + * 上传地址: https://upload.qiniup.com + * 请求方式: POST (multipart/form-data) + * 请求参数: + * - token: 上传凭证(本接口返回) + * - key: 文件存储 Key(本接口返回) + * - file: 文件内容 + */ + post: operations["get_upload_token_api_v1_qiniu_upload_token_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/qiniu/upload/audio": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Upload Audio + * @description 上传音频文件 + * + * 支持格式: MP3, WAV, M4A, AAC, OGG + * 文件会自动存储到: audios/{userId}/{date}/{uuid}.{ext} + */ + post: operations["upload_audio_api_v1_qiniu_upload_audio_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/qiniu/upload/video": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Upload Video + * @description 上传视频文件 + * + * 支持格式: MP4, MOV, AVI, WebM + * 文件会自动存储到: videos/{userId}/{date}/{uuid}.{ext} + */ + post: operations["upload_video_api_v1_qiniu_upload_video_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/qiniu/upload/avatar": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Upload Avatar + * @description 上传形象克隆视频 + * + * 用于形象克隆功能,上传的视频将同时用于创建自定义音色和主体。 + * + * KlingAI 要求: + * - 格式: MP4, MOV + * - 时长: 5-30 秒 (建议 5-8 秒) + * - 大小: 不超过 200MB + * - 分辨率: 高度 720px~2160px + * - 内容: 写实风格人物正面特写,人脸清晰、无遮挡,视频中有清晰人声 + * + * 文件存储路径: meijiaka/avatars/{userId}/{date}/{uuid}.{ext} + * + * 重复检测: + * - 如果提供了 fileHash,会检查是否已有相同文件的任务在进行中 + * - 返回的 isDuplicate 表示是否复用了已有资源 + * - existingTaskId 表示已存在任务的ID(如果有) + */ + post: operations["upload_avatar_api_v1_qiniu_upload_avatar_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/qiniu/files/{key}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get File Info + * @description 获取文件信息 + * + * Args: + * key: 文件存储 Key(路径格式) + */ + get: operations["get_file_info_api_v1_qiniu_files__key__get"]; + put?: never; + post?: never; + /** + * Delete File + * @description 删除文件 + * + * Args: + * key: 文件存储 Key + */ + delete: operations["delete_file_api_v1_qiniu_files__key__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/qiniu/refresh-cdn": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Refresh Cdn + * @description 刷新 CDN 缓存 + * + * 文件更新后,调用此接口刷新 CDN 缓存,确保用户访问到最新内容。 + */ + post: operations["refresh_cdn_api_v1_qiniu_refresh_cdn_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/video/generate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create Video Generation + * @description 创建视频生成任务 + * + * 接收项目ID、数字人ID和分镜列表,创建视频生成作业。 + * 支持 SSE 流式查询进度。 + * + * **分镜类型说明:** + * - `segment`: 分镜(带数字人),使用 omni-video 接口,需要 element_id + * - `empty_shot`: 空镜,使用文生图 + 图生视频流程 + * + * **调用流程:** + * 1. 调用此接口创建任务,获取 job_id + * 2. 使用 SSE 接口 `/video/jobs/{job_id}/stream` 监听进度 + * 3. 或使用 `/video/jobs/{job_id}` 查询状态 + */ + post: operations["create_video_generation_api_v1_video_generate_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/video/jobs/{job_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Video Job + * @description 查询视频生成作业详情 + * + * 获取指定作业的详细信息和所有分镜的处理结果。 + */ + get: operations["get_video_job_api_v1_video_jobs__job_id__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/video/jobs/{job_id}/stream": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Stream Video Job + * @description SSE 流式获取视频生成进度 + * + * 使用 Server-Sent Events 实时推送视频生成进度。 + * + * **事件类型:** + * - `start`: 开始生成 + * - `processing`: 处理中(包含进度信息) + * - `finalizing`: 完成整理 + * - `complete`: 全部完成 + * - `error`: 发生错误 + * + * **示例:** + * ``` + * const eventSource = new EventSource('/api/v1/video/jobs/{job_id}/stream'); + * eventSource.onmessage = (e) => { + * const data = JSON.parse(e.data); + * console.log(data.progress + '%: ' + data.message); + * }; + * ``` + */ + get: operations["stream_video_job_api_v1_video_jobs__job_id__stream_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/video/jobs/{job_id}/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Video Job Status + * @description 获取视频生成作业状态(简化版) + */ + get: operations["get_video_job_status_api_v1_video_jobs__job_id__status_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/video/library": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Digital Humans + * @description 获取数字人素材库 + * + * 返回系统预设的数字人列表。 + */ + get: operations["get_digital_humans_api_v1_video_library_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/video/upload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Upload Video + * @description 上传人物视频作为数字人素材 + * + * 文件要求: + * - 格式:mp4, mov + * - 时长:2-60秒 + * - 分辨率:720p 或 1080p + */ + post: operations["upload_video_api_v1_video_upload_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/video/{video_id}/download": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Download Video + * @description 下载视频文件 + * + * 支持三种查找位置: + * 1. data/video/{video_id}.mp4 - 传统存储 + * 2. data/uploads/{video_id}.ext - 上传文件 + * 3. ~/Documents/Meijiaka/projects/*\/videos/{video_id}.mp4 - 项目生成的视频 + * 文件名格式: scene_{shot_id}.mp4 + */ + get: operations["download_video_api_v1_video__video_id__download_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/video/{video_id}/thumbnail": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Video Thumbnail + * @description 获取视频缩略图 + */ + get: operations["get_video_thumbnail_api_v1_video__video_id__thumbnail_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/avatar/clone": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Clone Avatar + * @description 提交形象克隆任务 + * + * 立即返回 task_id,前端通过 SSE 或轮询跟踪进度。 + * 实际串行流程由 Celery Worker 异步执行。 + */ + post: operations["clone_avatar_api_v1_avatar_clone_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/avatar/tasks/{task_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Avatar Task Status + * @description 查询形象克隆任务状态 + */ + get: operations["get_avatar_task_status_api_v1_avatar_tasks__task_id__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/avatar/clone/stream": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Sse Avatar Clone + * @description SSE 流:实时推送形象克隆任务状态 + * + * 前端连接后,每 3 秒推送一次状态,直到任务结束(succeed / failed / timeout)。 + */ + get: operations["sse_avatar_clone_api_v1_avatar_clone_stream_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/avatar/tasks/{task_id}/retry": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Retry Avatar Task + * @description 重试失败或超时的形象克隆任务 + * + * 仅允许对 voice_failed / element_failed / timeout 状态的任务重试。 + * 重试时会重置状态为 pending 并重新派发 Celery 任务。 + */ + post: operations["retry_avatar_task_api_v1_avatar_tasks__task_id__retry_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/avatar/{avatar_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** + * Delete Avatar + * @description 删除形象:软删除 DB 记录 + 异步清理 Kling 资源 + */ + delete: operations["delete_avatar_api_v1_avatar__avatar_id__delete"]; + options?: never; + head?: never; + /** + * Update Avatar Name + * @description 更新形象名称 + * + * 同步更新本地和后端 DB,保证数据一致。 + */ + patch: operations["update_avatar_name_api_v1_avatar__avatar_id__patch"]; + trace?: never; + }; + "/api/v1/avatar/library": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Avatar Library + * @description 获取当前用户的克隆形象库 + * + * 返回 DB 中状态为 succeed 且未软删除的记录,供前端与 localStorage 合并。 + */ + get: operations["get_avatar_library_api_v1_avatar_library_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/avatar/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Avatar Health + * @description 获取形象克隆服务健康状态 + * + * 普通用户只能看到自己的任务统计,管理员可以看到全部。 + */ + get: operations["get_avatar_health_api_v1_avatar_health_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/avatar/admin/trigger-recovery": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Admin Trigger Recovery + * @description 手动触发卡住任务恢复(管理员接口) + * + * 立即执行一次 check_and_recover_stuck_tasks 任务。 + */ + post: operations["admin_trigger_recovery_api_v1_avatar_admin_trigger_recovery_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/system/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * System Health + * @description 系统健康检查(详细版) + */ + get: operations["system_health_api_v1_system_health_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/system/version": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * System Version + * @description 获取系统版本信息 + */ + get: operations["system_version_api_v1_system_version_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/caption/submit": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Submit Caption Task + * @description 提交字幕生成任务 + * + * 提交音频/视频文件URL,生成带时间轴的字幕。 + */ + post: operations["submit_caption_task_api_v1_caption_submit_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/caption/query/{task_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Query Caption Task + * @description 查询字幕任务结果 + * + * Args: + * task_id: 任务ID + * blocking: 是否阻塞等待结果 (默认True) + */ + get: operations["query_caption_task_api_v1_caption_query__task_id__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/caption/generate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Generate Caption + * @description 生成字幕(完整流程) + * + * 提交任务并轮询结果,直接返回最终字幕数据。 + * 适用于不需要异步处理的场景。 + */ + post: operations["generate_caption_api_v1_caption_generate_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/caption/generate-ass": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Generate Ass + * @description 生成 ASS 格式字幕(完整流程,使用抖音美好体) + * + * Args: + * video_width: 视频宽度(默认 1080) + * video_height: 视频高度(默认 1920) + */ + post: operations["generate_ass_api_v1_caption_generate_ass_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/caption/generate-srt": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Generate Srt + * @description 生成 SRT 格式字幕(完整流程) + * + * 直接返回 SRT 格式字幕文件内容。 + */ + post: operations["generate_srt_api_v1_caption_generate_srt_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/caption/ata/submit": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Submit Auto Align Task + * @description 提交自动字幕打轴任务 + * + * 为已有字幕文本自动配上时间轴。 + */ + post: operations["submit_auto_align_task_api_v1_caption_ata_submit_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/caption/ata/query/{task_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Query Auto Align Task + * @description 查询打轴任务结果 + */ + get: operations["query_auto_align_task_api_v1_caption_ata_query__task_id__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/caption/ata/align": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Auto Align Caption + * @description 自动字幕打轴(完整流程) + * + * 提交打轴任务并轮询结果,直接返回最终数据。 + */ + post: operations["auto_align_caption_api_v1_caption_ata_align_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/caption/convert/ass": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Convert To Ass + * @description 将字幕结果转换为 ASS 格式(使用抖音美好体) + */ + post: operations["convert_to_ass_api_v1_caption_convert_ass_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/caption/convert/srt": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Convert To Srt + * @description 将字幕结果转换为 SRT 格式 + * + * 用于将 /generate 返回的原始数据转换为 SRT 格式。 + */ + post: operations["convert_to_srt_api_v1_caption_convert_srt_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/caption/convert/vtt": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Convert To Vtt + * @description 将字幕结果转换为 WebVTT 格式 + */ + post: operations["convert_to_vtt_api_v1_caption_convert_vtt_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/tasks/{task_type}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create Task + * @description 创建新任务 + * + * 根据任务类型派发对应的 Celery 任务 + */ + post: operations["create_task_api_v1_tasks__task_type__post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/tasks/{task_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Task Status + * @description 查询任务状态 + * + * 前端通过轮询此接口获取任务进度 + */ + get: operations["get_task_status_api_v1_tasks__task_id__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/tasks/{task_id}/result": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Task Result + * @description 获取任务结果(简化接口,直接返回 result 字段) + */ + get: operations["get_task_result_api_v1_tasks__task_id__result_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Health Check + * @description 服务健康检查 + */ + get: operations["health_check_health_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Root + * @description API 根路径 + */ + get: operations["root__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** + * AdvancedLipSyncRequest + * @description 新版对口型请求(advanced-lip-sync) + */ + AdvancedLipSyncRequest: { + /** + * Session Id + * @description 人脸识别返回的会话ID + */ + session_id: string; + /** + * Face Choose + * @description 人脸对口型配置列表 + */ + face_choose: components["schemas"]["FaceChooseItem"][]; + /** + * Callback Url + * @description 回调通知地址 + */ + callback_url?: string | null; + }; + /** + * AiMultiShotRequest + * @description 智能补全主体图请求 + */ + AiMultiShotRequest: { + /** + * Frontal Image + * @description 主体正面参考图 URL + * @example https://example.com/front.jpg + */ + frontal_image: string; + /** + * Callback Url + * @description 回调通知地址 + */ + callback_url?: string | null; + }; + /** + * AiMultiShotResponse + * @description 智能补全主体图响应 + */ + AiMultiShotResponse: { + /** Task Id */ + task_id: string; + /** Task Status */ + task_status: string; + /** Created At */ + created_at: number; + /** Updated At */ + updated_at: number; + }; + /** + * ApiResponse[AiMultiShotResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_AiMultiShotResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["AiMultiShotResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[AutoAlignResult] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_AutoAlignResult_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["AutoAlignResult"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[AvatarHealthResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_AvatarHealthResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["AvatarHealthResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[AvatarTaskStatusResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_AvatarTaskStatusResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["AvatarTaskStatusResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[CaptionResult] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_CaptionResult_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["CaptionResult-Output"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[CaptionTaskResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_CaptionTaskResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["CaptionTaskResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[CloneAvatarResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_CloneAvatarResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["CloneAvatarResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[CreateCustomVoiceResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_CreateCustomVoiceResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["CreateCustomVoiceResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[DigitalHuman] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_DigitalHuman_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["DigitalHuman"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[ElementResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_ElementResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["ElementResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[FileUploadResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_FileUploadResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["FileUploadResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[GenerateResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_GenerateResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["GenerateResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[HealthResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_HealthResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["HealthResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[IdentifyFaceResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_IdentifyFaceResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["IdentifyFaceResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[LoginResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_LoginResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["LoginResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[ModelHealthResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_ModelHealthResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["ModelHealthResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[PolishResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_PolishResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["PolishResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[PromptTemplatesResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_PromptTemplatesResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["PromptTemplatesResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[ScriptGenerateResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_ScriptGenerateResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["ScriptGenerateResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[SrtSubtitleResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_SrtSubtitleResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["SrtSubtitleResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[TaskStatusResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_TaskStatusResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["app__api__v1__klingai__TaskStatusResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[TestModelResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_TestModelResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["TestModelResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[UploadTokenResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_UploadTokenResponse_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["UploadTokenResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[VideoJobDetail] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_VideoJobDetail_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["VideoJobDetail"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[VideoJobStatus] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_VideoJobStatus_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["VideoJobStatus"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[dict] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_dict_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** + * Data + * @description 响应数据 + */ + data?: Record | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[list[AvatarItem]] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_list_AvatarItem__: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** + * Data + * @description 响应数据 + */ + data?: components["schemas"]["AvatarItem"][] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[list[DigitalHuman]] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_list_DigitalHuman__: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** + * Data + * @description 响应数据 + */ + data?: components["schemas"]["DigitalHuman"][] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[list[ElementResponse]] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_list_ElementResponse__: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** + * Data + * @description 响应数据 + */ + data?: components["schemas"]["ElementResponse"][] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[list[ModelResponse]] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_list_ModelResponse__: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** + * Data + * @description 响应数据 + */ + data?: components["schemas"]["ModelResponse"][] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[list[PlatformResponse]] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_list_PlatformResponse__: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** + * Data + * @description 响应数据 + */ + data?: components["schemas"]["PlatformResponse"][] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[list[ScriptShot]] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_list_ScriptShot__: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** + * Data + * @description 响应数据 + */ + data?: components["schemas"]["ScriptShot"][] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[list[VoiceInfo]] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_list_VoiceInfo__: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** + * Data + * @description 响应数据 + */ + data?: components["schemas"]["VoiceInfo"][] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[str] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + ApiResponse_str_: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** + * Data + * @description 响应数据 + */ + data?: string | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * AutoAlignResult + * @description 自动字幕打轴结果 + */ + AutoAlignResult: { + /** + * Code + * @description 状态码: 0=成功, 2000=处理中 + */ + code: number; + /** + * Message + * @description 状态信息 + */ + message: string; + /** + * Duration + * @description 音频时长(秒) + */ + duration: number; + /** + * Utterances + * @description 打轴后的字幕时间轴 + */ + utterances?: components["schemas"]["CaptionUtterance"][] | null; + }; + /** + * AutoAlignSubmitRequest + * @description 自动字幕打轴任务提交请求 + */ + AutoAlignSubmitRequest: { + /** + * Audio Url + * @description 音频/视频文件URL + */ + audio_url: string; + /** + * Audio Text + * @description 要打轴的字幕文本 + */ + audio_text: string; + /** + * Caption Type + * @description 识别类型: speech(说话), singing(歌词) + * @default speech + */ + caption_type: string; + /** + * Sta Punc Mode + * @description 标点模式: 1=省略句末, 2=空格代替, 3=保留完整 + * @default 3 + */ + sta_punc_mode: number; + }; + /** + * AvatarHealthResponse + * @description 形象克隆服务健康状态 + */ + AvatarHealthResponse: { + /** + * Total Processing + * @description 处理中的任务总数 + */ + total_processing: number; + /** + * Pending + * @description 待处理任务数 + */ + pending: number; + /** + * Voice Processing + * @description 音色生成中任务数 + */ + voice_processing: number; + /** + * Element Processing + * @description 主体生成中任务数 + */ + element_processing: number; + /** + * Stuck Tasks + * @description 卡住任务数(超过30分钟) + */ + stuck_tasks: number; + /** + * Recent Failures + * @description 最近1小时失败数 + */ + recent_failures: number; + }; + /** + * AvatarItem + * @description 形象库列表项 + */ + AvatarItem: { + /** + * Id + * @description 形象唯一标识 + */ + id: string; + /** + * Name + * @description 展示名称 + */ + name: string; + /** + * Voice Id + * @description Kling 自定义音色 ID + */ + voice_id: string; + /** + * Element Id + * @description Kling 主体 ID + */ + element_id: number; + /** + * Video Url + * @description 原始人物视频 URL + */ + video_url: string; + /** + * Trial Url + * @description 音色试听 URL + */ + trial_url?: string | null; + /** + * Record Time + * @description 创建时间 ISO 字符串 + */ + record_time: string; + }; + /** + * AvatarTaskStatusResponse + * @description 任务状态查询响应 + */ + AvatarTaskStatusResponse: { + /** Task Id */ + task_id: string; + /** + * Status + * @description 当前状态 + */ + status: string; + /** + * Fail Reason + * @description 失败原因 + */ + fail_reason?: string | null; + /** + * Voice Id + * @description 已生成的音色 ID + */ + voice_id?: string | null; + /** + * Element Id + * @description 已生成的主体 ID + */ + element_id?: number | null; + /** + * Trial Url + * @description 试听 URL + */ + trial_url?: string | null; + /** + * Video Url + * @description 原始视频 URL + */ + video_url: string; + /** + * Name + * @description 形象名称 + */ + name: string; + /** + * Created At + * Format: date-time + * @description 创建时间 + */ + created_at: string; + /** + * Updated At + * Format: date-time + * @description 更新时间 + */ + updated_at: string; + }; + /** Body_upload_audio_api_v1_qiniu_upload_audio_post */ + Body_upload_audio_api_v1_qiniu_upload_audio_post: { + /** + * File + * @description 音频文件(MP3, WAV, M4A, AAC, OGG) + */ + file: string; + /** + * Userid + * @description 用户ID(可选,用于目录隔离) + */ + userId?: string | null; + }; + /** Body_upload_avatar_api_v1_qiniu_upload_avatar_post */ + Body_upload_avatar_api_v1_qiniu_upload_avatar_post: { + /** + * File + * @description 形象克隆视频(MP4, MOV) + */ + file: string; + /** + * Userid + * @description 用户ID(可选,用于目录隔离) + */ + userId?: string | null; + /** + * Filehash + * @description 前端计算的文件SHA256哈希,用于重复检测 + */ + fileHash?: string | null; + }; + /** Body_upload_video_api_v1_qiniu_upload_video_post */ + Body_upload_video_api_v1_qiniu_upload_video_post: { + /** + * File + * @description 视频文件(MP4, MOV, AVI, WebM) + */ + file: string; + /** + * Userid + * @description 用户ID(可选,用于目录隔离) + */ + userId?: string | null; + }; + /** Body_upload_video_api_v1_video_upload_post */ + Body_upload_video_api_v1_video_upload_post: { + /** + * File + * @description 视频文件 + */ + file: string; + /** + * Name + * @description 数字人名称 + */ + name?: string | null; + }; + /** + * CaptionResult + * @description 字幕生成结果 + */ + "CaptionResult-Input": { + /** + * Code + * @description 状态码: 0=成功, 2000=处理中 + */ + code: number; + /** + * Message + * @description 状态信息 + */ + message: string; + /** + * Duration + * @description 音频时长(秒) + */ + duration: number; + /** + * Utterances + * @description 字幕时间轴列表 + */ + utterances?: components["schemas"]["CaptionUtterance"][] | null; + }; + /** + * CaptionResult + * @description 字幕生成结果 + */ + "CaptionResult-Output": { + /** + * Code + * @description 状态码: 0=成功, 2000=处理中 + */ + code: number; + /** + * Message + * @description 状态信息 + */ + message: string; + /** + * Duration + * @description 音频时长(秒) + */ + duration: number; + /** + * Utterances + * @description 字幕时间轴列表 + */ + utterances?: components["schemas"]["CaptionUtterance"][] | null; + }; + /** + * CaptionSubmitRequest + * @description 字幕生成任务提交请求 + */ + CaptionSubmitRequest: { + /** + * Audio Url + * @description 音频/视频文件URL + */ + audio_url: string; + /** + * Language + * @description 语言: zh-CN, en-US, ja-JP, ko-KR, es-MX, ru-RU, fr-FR, yue, wuu, nan, ug + * @default zh-CN + */ + language: string; + /** + * Caption Type + * @description 识别类型: auto(自动), speech(说话), singing(歌词) + * @default auto + */ + caption_type: string; + /** + * Use Punc + * @description 自动标点: True/False + * @default true + */ + use_punc: boolean; + /** + * Use Itn + * @description 数字转换: True(中文数字转阿拉伯数字) + * @default true + */ + use_itn: boolean; + /** + * Words Per Line + * @description 每行字数 + * @default 46 + */ + words_per_line: number; + /** + * Max Lines + * @description 每屏行数 + * @default 1 + */ + max_lines: number; + }; + /** + * CaptionTaskResponse + * @description 字幕任务提交响应 + */ + CaptionTaskResponse: { + /** + * Task Id + * @description 任务ID + */ + task_id: string; + /** + * Status + * @description 任务状态: pending/processing/completed/failed + */ + status: string; + }; + /** + * CaptionUtterance + * @description 一句话/一段字幕的时间轴信息 + */ + CaptionUtterance: { + /** + * Text + * @description 文本内容 + */ + text: string; + /** + * Start Time + * @description 开始时间(毫秒) + */ + start_time: number; + /** + * End Time + * @description 结束时间(毫秒) + */ + end_time: number; + /** + * Words + * @description 字词级时间轴 + */ + words?: components["schemas"]["CaptionWord"][] | null; + }; + /** + * CaptionWord + * @description 单个字/词的时间轴信息 + */ + CaptionWord: { + /** + * Text + * @description 字/词内容 + */ + text: string; + /** + * Start Time + * @description 开始时间(毫秒) + */ + start_time: number; + /** + * End Time + * @description 结束时间(毫秒) + */ + end_time: number; + }; + /** + * CloneAvatarRequest + * @description 创建形象克隆请求 + */ + CloneAvatarRequest: { + /** + * Name + * @description 形象名称 + */ + name: string; + /** + * Video Url + * @description 人物视频 URL + */ + video_url: string; + }; + /** + * CloneAvatarResponse + * @description 创建形象克隆响应 + */ + CloneAvatarResponse: { + /** + * Task Id + * @description 任务 ID(用于 SSE/轮询跟踪进度) + */ + task_id: string; + /** + * Status + * @description 初始状态 + * @default pending + */ + status: string; + }; + /** + * CreateCustomVoiceRequest + * @description 创建自定义音色请求 + */ + CreateCustomVoiceRequest: { + /** + * Voice Name + * @description 音色名称(最多20字符) + * @example 我的音色 + */ + voice_name: string; + /** + * Audio Url + * @description 音频文件URL(mp3/wav/mp4/mov) + */ + audio_url?: string | null; + /** + * Video Url + * @description 视频文件URL + */ + video_url?: string | null; + /** + * Video Id + * @description 历史作品ID(v2.6/sound=on/数字人/对口型生成的视频) + */ + video_id?: string | null; + /** + * Callback Url + * @description 回调通知地址 + */ + callback_url?: string | null; + /** + * External Task Id + * @description 自定义任务ID + */ + external_task_id?: string | null; + }; + /** + * CreateCustomVoiceResponse + * @description 创建自定义音色响应 + */ + CreateCustomVoiceResponse: { + /** Task Id */ + task_id: string; + /** Task Status */ + task_status: string; + /** Created At */ + created_at: number; + /** Updated At */ + updated_at: number; + }; + /** + * CreateElementRequest + * @description 创建主体请求 + */ + CreateElementRequest: { + /** + * Element Name + * @description 主体名称(最多20字符) + * @example 我的小猫 + */ + element_name: string; + /** + * Element Description + * @description 主体描述(最多100字符) + * @example 一只橘色的小猫,毛茸茸的 + */ + element_description: string; + /** + * Reference Type + * @description 参考类型: image_refer 或 video_refer + * @default image_refer + */ + reference_type: string; + /** + * Element Image List + * @description 图片参考列表(图片定制时必填,第一个作为正面图) + */ + element_image_list?: components["schemas"]["ElementImage"][] | null; + /** + * Element Video List + * @description 视频参考列表(视频定制时必填,第一个作为正面视频) + */ + element_video_list?: components["schemas"]["ElementVideo"][] | null; + /** + * Element Voice Id + * @description 音色ID,绑定音色到主体 + */ + element_voice_id?: string | null; + /** + * Callback Url + * @description 回调通知地址 + */ + callback_url?: string | null; + }; + /** + * DigitalHuman + * @description 数字人信息 + */ + DigitalHuman: { + /** Id */ + id: string; + /** Name */ + name: string; + /** Desc */ + desc: string; + /** Avatar Url */ + avatar_url?: string | null; + /** + * Type + * @default preset + */ + type: string; + }; + /** + * ElementImage + * @description 主体参考图片(对应 KlingAI 官方格式 imageUrl) + */ + ElementImage: { + /** + * Image Url + * @description 图片URL + */ + image_url: string; + /** + * Name + * @description 图片名称 + */ + name?: string | null; + }; + /** + * ElementResponse + * @description 主体响应 + */ + ElementResponse: { + /** Element Id */ + element_id?: number | null; + /** Element Name */ + element_name?: string | null; + /** Element Description */ + element_description?: string | null; + /** Element Type */ + element_type?: string | null; + /** Status */ + status?: string | null; + /** Task Id */ + task_id?: string | null; + /** Task Status */ + task_status?: string | null; + /** Created At */ + created_at?: number | null; + /** Updated At */ + updated_at?: number | null; + /** Element Image List */ + element_image_list?: Record | null; + /** Element Video List */ + element_video_list?: Record | null; + /** Element Voice Info */ + element_voice_info?: Record | null; + /** Owned By */ + owned_by?: string | null; + }; + /** + * ElementVideo + * @description 主体参考视频(对应 KlingAI 官方格式 videoUrl) + */ + ElementVideo: { + /** + * Video Url + * @description 视频URL + */ + video_url: string; + /** + * Name + * @description 视频名称 + */ + name?: string | null; + }; + /** + * FaceChooseItem + * @description 新版对口型人脸配置 + */ + FaceChooseItem: { + /** + * Face Id + * @description 人脸ID,由 identify-face 接口返回 + */ + face_id: string; + /** + * Audio Id + * @description 通过TTS生成的音频ID(与 sound_file 二选一) + */ + audio_id?: string | null; + /** + * Sound File + * @description 音频文件URL或Base64(与 audio_id 二选一) + */ + sound_file?: string | null; + /** + * Sound Start Time + * @description 音频裁剪起点时间(ms) + * @default 0 + */ + sound_start_time: number; + /** + * Sound End Time + * @description 音频裁剪终点时间(ms) + */ + sound_end_time: number; + /** + * Sound Insert Time + * @description 裁剪后音频插入时间(ms) + * @default 0 + */ + sound_insert_time: number; + }; + /** + * FileUploadResponse + * @description 文件上传响应 + */ + FileUploadResponse: { + /** Key */ + key: string; + /** Url */ + url: string; + /** Hash */ + hash: string; + /** Mimetype */ + mimeType: string; + /** Fsize */ + fsize: number; + /** + * Isduplicate + * @default false + */ + isDuplicate: boolean; + /** Message */ + message?: string | null; + /** Existingtaskid */ + existingTaskId?: string | null; + }; + /** + * GenerateRequest + * @description 生成请求 + */ + GenerateRequest: { + /** + * Prompt + * @description 提示词 + */ + prompt: string; + /** + * Model Id + * @description 指定模型 ID + */ + model_id?: string | null; + /** + * Task Type + * @description 任务类型,用于自动选模型: script/polish + */ + task_type?: string | null; + /** + * Temperature + * @description 随机性 (0-2) + */ + temperature?: number | null; + /** + * Max Tokens + * @description 最大生成长度 + */ + max_tokens?: number | null; + }; + /** + * GenerateResponse + * @description 生成响应 + */ + GenerateResponse: { + /** Content */ + content: string; + /** Model */ + model: string; + /** Usage */ + usage: Record | null; + }; + /** + * GenerateScriptRequest + * @description 生成脚本请求 + */ + GenerateScriptRequest: { + /** + * Topic + * @description 创作主题/灵感 + */ + topic: string; + /** + * Duration + * @description 视频时长(秒) + * @default 45 + */ + duration: number; + /** + * Script Type + * @description 脚本类型 + * @default 干货型 + */ + script_type: string; + /** + * Model + * @description 指定模型(可选) + */ + model?: string | null; + }; + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: components["schemas"]["ValidationError"][]; + }; + /** + * HealthResponse + * @description 健康检查响应 + */ + HealthResponse: { + /** Status */ + status: string; + /** Total Models */ + total_models: number; + /** Available Models */ + available_models: number; + /** Models */ + models: Record[]; + }; + /** + * IdentifyFaceRequest + * @description 人脸识别请求 + */ + IdentifyFaceRequest: { + /** + * Video Id + * @description KlingAI 生成的视频 ID + */ + video_id?: string | null; + /** + * Video Url + * @description 上传的视频 URL(与 videoId 二选一 + */ + video_url?: string | null; + }; + /** + * IdentifyFaceResponse + * @description 人脸识别响应 + */ + IdentifyFaceResponse: { + /** Session Id */ + session_id: string; + /** Face Data */ + face_data: Record[]; + }; + /** + * Image2VideoRequest + * @description 图生视频请求 + */ + Image2VideoRequest: { + /** + * Image Url + * @description 输入图片 URL + */ + image_url: string; + /** + * Prompt + * @description 视频运动描述提示词 + */ + prompt?: string | null; + /** + * Model + * @description 视频模型 + * @default kling-v2.6 + */ + model: string | null; + /** + * Duration + * @description 视频时长(秒) + * @default 5 + */ + duration: number; + /** + * Aspect Ratio + * @description 宽高比 + */ + aspect_ratio?: string | null; + /** + * Mode + * @description 生成模式 + * @default pro + */ + mode: string; + /** + * Callback Url + * @description 回调通知地址 + */ + callback_url?: string | null; + }; + /** + * ImageGenerateRequest + * @description 图像生成请求 + */ + ImageGenerateRequest: { + /** + * Prompt + * @description 图像描述提示词 + */ + prompt: string; + /** + * Model + * @description 图像模型: kolors-v1 + * @default kolors-v1 + */ + model: string | null; + /** + * Width + * @description 图像宽度 + * @default 1024 + */ + width: number; + /** + * Height + * @description 图像高度 + * @default 1024 + */ + height: number; + /** + * Negative Prompt + * @description 负面提示词 + */ + negative_prompt?: string | null; + /** + * Callback Url + * @description 回调通知地址 + */ + callback_url?: string | null; + }; + /** + * LoginResponse + * @description 登录响应 + */ + LoginResponse: { + /** + * Token + * @description JWT 访问令牌 + */ + token: string; + user: components["schemas"]["UserInfo"]; + }; + /** + * MobileLoginRequest + * @description 手机号登录请求 + */ + MobileLoginRequest: { + /** + * Mobile + * @description 手机号 + */ + mobile: string; + /** + * Nickname + * @description 用户昵称 + */ + nickname?: string | null; + }; + /** + * ModelHealthInfo + * @description 模型健康信息 + */ + ModelHealthInfo: { + /** + * Id + * @description 模型 ID + */ + id: string; + /** + * Name + * @description 模型名称 + */ + name: string; + /** + * Is Available + * @description 是否可用 + */ + is_available: boolean; + /** + * Response Time + * @description 响应时间(毫秒) + */ + response_time: number; + /** + * Last Error + * @description 上次错误信息 + */ + last_error?: string | null; + }; + /** + * ModelHealthResponse + * @description 模型健康检查响应 + */ + ModelHealthResponse: { + /** + * Status + * @description 整体状态:healthy / unhealthy / error + */ + status: string; + /** + * Models + * @description 各模型状态 + */ + models: components["schemas"]["ModelHealthInfo"][]; + /** @description 推荐的模型 */ + recommended_model?: components["schemas"]["ModelHealthInfo"] | null; + /** + * Total Models + * @description 模型总数 + */ + total_models: number; + /** + * Available Models + * @description 可用模型数 + */ + available_models: number; + /** + * Error + * @description 错误信息 + */ + error?: string | null; + }; + /** + * ModelResponse + * @description 模型响应 + */ + ModelResponse: { + /** Id */ + id: string; + /** Platform Id */ + platform_id: string; + /** Model Name */ + model_name: string; + /** Display Name */ + display_name: string; + /** Capabilities */ + capabilities: string[]; + /** Default Params */ + default_params: Record; + /** Is Enabled */ + is_enabled: boolean; + /** Full Model Id */ + full_model_id: string; + }; + /** + * OmniImageRequest + * @description Omni-Image 图像生成请求 + */ + OmniImageRequest: { + /** + * Prompt + * @description 图像描述提示词 + */ + prompt: string; + /** + * Model + * @description 模型: kling-image-o1, kling-v3-omni + * @default kling-image-o1 + */ + model: string | null; + /** + * Aspect Ratio + * @description 宽高比: 16:9/9:16/1:1/4:3/3:4 + * @default 9:16 + */ + aspect_ratio: string | null; + /** + * Resolution + * @description 清晰度: 1k/2k/4k + * @default 1k + */ + resolution: string | null; + /** + * Result Type + * @description 结果类型: single/series + * @default single + */ + result_type: string | null; + /** + * N + * @description 生成数量 1-9 + * @default 1 + */ + n: number | null; + /** + * Element List + * @description 主体参考列表 + */ + element_list?: Record[] | null; + /** + * Image List + * @description 参考图列表 + */ + image_list?: Record[] | null; + /** + * Callback Url + * @description 回调通知地址 + */ + callback_url?: string | null; + }; + /** + * OmniVideoRequest + * @description Omni-Video 视频生成请求(kling-v3-omni / kling-video-o1) + */ + OmniVideoRequest: { + /** + * Prompt + * @description 视频描述提示词(不超过2500字符),支持 <<>>/<<>>/<<>> 引用语法 + * @example 一只<<>>在花园里奔跑,阳光明媚 + */ + prompt: string; + /** + * Model + * @description 模型: kling-v3-omni, kling-video-o1 + * @default kling-v3-omni + */ + model: string | null; + /** + * Duration + * @description 视频时长(秒): 3/5/10/15,Omni支持3-15秒 + * @default 5 + */ + duration: number; + /** + * Aspect Ratio + * @description 宽高比: 16:9, 9:16, 1:1 + * @default 16:9 + */ + aspect_ratio: string; + /** + * Mode + * @description 生成模式: pro(高质量) 或 std(标准) + * @default pro + */ + mode: string; + /** + * Sound + * @description 声音控制: on=音画同出, off=无声 + * @default on + */ + sound: string; + /** + * Negative Prompt + * @description 负面提示词 + */ + negative_prompt?: string | null; + /** + * Multi Shot + * @description 是否启用多镜头模式 + * @default false + */ + multi_shot: boolean; + /** + * Shot Type + * @description 分镜方式: customize=自定义, intelligence=智能分镜 + */ + shot_type?: string | null; + /** + * Multi Prompt + * @description 多镜头提示词列表,每个元素包含 index, prompt, duration,最多6个分镜 + */ + multi_prompt?: (Record | null)[]; + /** + * Image List + * @description 参考图片列表,最多4张 + */ + image_list?: (Record | null)[]; + /** + * Element List + * @description 主体参考列表,格式: [{'elementId': 123}], 最多7个 + */ + element_list?: (Record | null)[]; + /** + * Video List + * @description 参考视频列表 + */ + video_list?: (Record | null)[]; + /** + * Callback Url + * @description 回调通知地址 + */ + callback_url?: string | null; + /** + * External Task Id + * @description 自定义任务ID + */ + external_task_id?: string | null; + }; + /** + * PlatformResponse + * @description 平台响应 + */ + PlatformResponse: { + /** Id */ + id: string; + /** Name */ + name: string; + /** Provider */ + provider: string; + }; + /** + * PolishResponse + * @description 润色响应 + */ + PolishResponse: { + /** Success */ + success: boolean; + /** Original */ + original: string; + /** Polished */ + polished: string | null; + /** Polish Type */ + polish_type: string; + /** Model */ + model: string; + /** Usage */ + usage: Record | null; + }; + /** + * PromptTemplatesResponse + * @description Prompt 模板配置响应 + */ + PromptTemplatesResponse: { + /** Script Types */ + script_types: Record[]; + /** Video Styles */ + video_styles: Record[]; + /** Tones */ + tones: string[]; + }; + /** + * ScriptGenerateRequest + * @description 脚本生成请求 + */ + ScriptGenerateRequest: { + /** + * Topic + * @description 脚本主题 + * @example 水电改造的3个致命错误 + */ + topic: string; + /** + * Duration + * @description 视频时长(秒) + * @default 30 + */ + duration: number; + /** + * Script Type + * @description 脚本类型 + * @default 干货型 + */ + script_type: string; + /** + * Video Style + * @description 视频风格 + * @default 口播 + */ + video_style: string; + /** + * Tone + * @description 语气风格 + */ + tone?: string | null; + /** + * Requirements + * @description 额外要求 + */ + requirements?: string | null; + /** + * Model Id + * @description 指定模型ID,默认使用系统默认模型 + */ + model_id?: string | null; + }; + /** + * ScriptGenerateResponse + * @description 脚本生成响应 - 针对前端展示优化 + */ + ScriptGenerateResponse: { + /** Success */ + success: boolean; + /** Script */ + script: (Record | null)[]; + /** Total Duration */ + total_duration: number | null; + /** Target Duration */ + target_duration: number; + /** Total Word Count */ + total_word_count: number | null; + /** Segment Count */ + segment_count: number | null; + /** Empty Shot Count */ + empty_shot_count: number | null; + /** Script Type */ + script_type: string; + /** Model */ + model: string; + /** Usage */ + usage: Record | null; + /** Error */ + error: string | null; + /** Raw Content */ + raw_content: string | null; + }; + /** + * ScriptShot + * @description 分镜/镜头定义 + */ + ScriptShot: { + /** + * Id + * @description 分镜序号 + */ + id: number; + /** + * Type + * @description 类型:segment(分镜) / empty_shot(空镜) + * @default segment + */ + type: string; + /** + * Scene + * @description 画面描述 + */ + scene?: string | null; + /** + * Voiceover + * @description 配音文案(空镜为空字符串) + */ + voiceover: string; + /** + * Duration + * @description 时长(如:5s) + * @default 5s + */ + duration: string; + /** + * Word Count + * @description 字数统计 + */ + word_count?: number | null; + }; + /** + * ShotData + * @description 分镜数据 + */ + ShotData: { + /** + * Id + * @description 分镜ID + */ + id: string; + /** + * Type + * @description 分镜类型: segment(分镜) 或 empty_shot(空镜) + * @default segment + */ + type: string; + /** + * Scene + * @description 场景描述 + * @default + */ + scene: string; + /** + * Voiceover + * @description 配音文案 + * @default + */ + voiceover: string; + /** + * Voice Id + * @description 音色ID(空镜时使用) + */ + voice_id?: string | null; + }; + /** + * ShotResult + * @description 单个分镜结果 + */ + ShotResult: { + /** Shot Id */ + shot_id: string; + /** Shot Type */ + shot_type: string; + /** Status */ + status: string; + /** Task Id */ + task_id?: string | null; + /** Video Url */ + video_url?: string | null; + /** Local Path */ + local_path?: string | null; + /** Error Message */ + error_message?: string | null; + }; + /** + * SrtSubtitleResponse + * @description SRT 字幕格式响应 + */ + SrtSubtitleResponse: { + /** + * Srt Content + * @description SRT 格式字幕内容 + */ + srt_content: string; + /** + * Utterances + * @description 原始时间轴数据 + */ + utterances: components["schemas"]["CaptionUtterance"][]; + }; + /** + * TaskCreateRequest + * @description 创建任务请求 + */ + TaskCreateRequest: { + /** + * Project Id + * @description 项目ID(可选) + */ + project_id?: string | null; + /** + * Params + * @description 任务参数 + */ + params?: Record; + }; + /** + * TaskCreateResponse + * @description 创建任务响应 + */ + TaskCreateResponse: { + /** + * Task Id + * @description 任务ID + */ + task_id: string; + /** + * Status + * @description 任务状态 + * @default pending + */ + status: string; + /** + * Message + * @description 状态消息 + * @default 任务已创建 + */ + message: string; + }; + /** + * TestModelRequest + * @description 测试模型请求 + */ + TestModelRequest: { + /** + * Model Id + * @description 要测试的模型 ID + */ + model_id?: string | null; + }; + /** + * TestModelResponse + * @description 测试模型响应 + */ + TestModelResponse: { + /** + * Success + * @description 是否成功 + */ + success: boolean; + /** + * Model + * @description 模型名称 + */ + model: string; + /** + * Response Time + * @description 响应时间(毫秒) + */ + response_time?: number | null; + /** + * Error + * @description 错误信息 + */ + error?: string | null; + /** + * Checked At + * @description 检查时间 ISO 格式 + */ + checked_at?: string | null; + }; + /** + * UpdateAvatarNameRequest + * @description 更新形象名称请求 + */ + UpdateAvatarNameRequest: { + /** + * Name + * @description 新形象名称 + */ + name: string; + }; + /** + * UploadTokenRequest + * @description 上传凭证请求 + */ + UploadTokenRequest: { + /** + * Key + * @description 文件存储 Key + */ + key: string; + /** + * Expires + * @description Token 有效期(秒) + * @default 3600 + */ + expires: number; + }; + /** + * UploadTokenResponse + * @description 上传凭证响应 + */ + UploadTokenResponse: { + /** Token */ + token: string; + /** Key */ + key: string; + /** + * Uploadurl + * @default https://upload.qiniup.com + */ + uploadUrl: string; + }; + /** + * UserInfo + * @description 用户信息 + */ + UserInfo: { + /** + * Id + * @description 用户 ID + */ + id: string; + /** + * Nickname + * @description 用户昵称 + */ + nickname: string; + /** + * Avatar + * @description 头像 URL + * @default + */ + avatar: string; + }; + /** ValidationError */ + ValidationError: { + /** Location */ + loc: (string | number)[]; + /** Message */ + msg: string; + /** Error Type */ + type: string; + /** Input */ + input?: unknown; + /** Context */ + ctx?: Record; + }; + /** + * VideoGenerateRequest + * @description 视频生成请求 + */ + VideoGenerateRequest: { + /** + * Project Id + * @description 项目ID + */ + project_id: string; + /** + * Element Id + * @description Kling主体ID(数字类型,分镜类型使用) + */ + element_id?: number | null; + /** + * Shots + * @description 分镜列表 + */ + shots: components["schemas"]["ShotData"][]; + }; + /** + * VideoJobDetail + * @description 视频作业详情 + */ + VideoJobDetail: { + /** Job Id */ + job_id: string; + /** Project Id */ + project_id: string; + /** Status */ + status: string; + /** Progress */ + progress: number; + /** Total Shots */ + total_shots: number; + /** Completed Shots */ + completed_shots: number; + /** Failed Shots */ + failed_shots: number; + /** Shots */ + shots: components["schemas"]["ShotResult"][]; + /** Created At */ + created_at: number; + /** Updated At */ + updated_at: number; + }; + /** + * VideoJobStatus + * @description 视频作业状态 + */ + VideoJobStatus: { + /** Job Id */ + job_id: string; + /** Project Id */ + project_id: string; + /** Status */ + status: string; + /** Progress */ + progress: number; + /** Total Shots */ + total_shots: number; + /** Completed Shots */ + completed_shots: number; + /** Failed Shots */ + failed_shots: number; + /** Created At */ + created_at: number; + /** Updated At */ + updated_at: number; + /** Error Message */ + error_message?: string | null; + }; + /** + * VirtualTryonRequest + * @description 虚拟试穿请求 + */ + VirtualTryonRequest: { + /** + * Person Image Url + * @description 人物图片 URL + */ + person_image_url: string; + /** + * Cloth Image Url + * @description 衣服图片 URL + */ + cloth_image_url: string; + /** + * Callback Url + * @description 回调通知地址 + */ + callback_url?: string | null; + }; + /** + * VoiceInfo + * @description 音色信息 + */ + VoiceInfo: { + /** Voice Id */ + voice_id: string; + /** Voice Name */ + voice_name: string; + /** Trial Url */ + trial_url?: string | null; + /** Owned By */ + owned_by?: string | null; + /** Status */ + status?: string | null; + }; + /** + * PolishRequest + * @description 润色请求 + */ + app__api__v1__ai_models__PolishRequest: { + /** + * Content + * @description 需要润色的内容 + */ + content: string; + /** + * Polish Type + * @description 润色类型:scene/voiceover + * @default voiceover + */ + polish_type: string; + /** + * Model Id + * @description 指定模型ID + */ + model_id?: string | null; + }; + /** + * TaskStatusResponse + * @description 任务状态响应 + */ + app__api__v1__klingai__TaskStatusResponse: { + /** Task Id */ + task_id: string; + /** Task Status */ + task_status: string; + /** Created At */ + created_at: number; + /** Updated At */ + updated_at: number; + /** Video Url */ + video_url?: string | null; + /** Image Url */ + image_url?: string | null; + /** Error Message */ + error_message?: string | null; + }; + /** + * VideoGenerateResponse + * @description 视频生成响应 + */ + app__api__v1__klingai__VideoGenerateResponse: { + /** Task Id */ + task_id: string; + /** Task Status */ + task_status: string; + /** Created At */ + created_at: number; + /** Updated At */ + updated_at: number; + }; + /** + * TaskStatusResponse + * @description 任务状态响应 + */ + app__api__v1__tasks__TaskStatusResponse: { + /** + * Task Id + * @description 任务ID + */ + task_id: string; + /** + * Type + * @description 任务类型 + */ + type?: string | null; + /** + * Status + * @description 任务状态: pending/running/waiting/completed/failed + */ + status: string; + /** + * Progress + * @description 进度百分比 (0-100) + * @default 0 + */ + progress: number; + /** + * Message + * @description 状态描述 + * @default + */ + message: string; + /** + * Result + * @description 任务结果(完成时) + */ + result?: Record | null; + /** + * Error + * @description 错误信息(失败时) + */ + error?: string | null; + }; + /** + * VideoGenerateResponse + * @description 视频生成响应 + */ + app__api__v1__video__VideoGenerateResponse: { + /** + * Job Id + * @description 作业ID + */ + job_id: string; + /** + * Task Id + * @description 任务ID(与job_id相同,兼容前端) + */ + task_id: string; + /** + * Status + * @description 作业状态 + */ + status: string; + /** + * Message + * @description 状态消息 + */ + message: string; + /** + * Sse Url + * @description SSE进度流URL + */ + sse_url: string; + }; + /** + * ApiResponse[VideoGenerateResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + app__schemas__common__ApiResponse_VideoGenerateResponse___1: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["app__api__v1__klingai__VideoGenerateResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * ApiResponse[VideoGenerateResponse] + * @example { + * "code": 200, + * "data": {}, + * "message": "success" + * } + */ + app__schemas__common__ApiResponse_VideoGenerateResponse___2: { + /** + * Code + * @description 状态码,200 表示成功 + * @default 200 + */ + code: number; + /** @description 响应数据 */ + data?: components["schemas"]["app__api__v1__video__VideoGenerateResponse"] | null; + /** + * Message + * @description 提示信息 + * @default success + */ + message: string; + }; + /** + * PolishRequest + * @description 润色请求 + */ + app__schemas__script__PolishRequest: { + /** + * Content + * @description 待润色内容 + */ + content: string; + /** + * Polish Type + * @description 润色类型:scene / voiceover + * @default voiceover + */ + polish_type: string; + /** + * Shot Type + * @description 镜头类型:segment(分镜) / empty_shot(空镜),用于画面润色时区分 + * @default segment + */ + shot_type: string | null; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + login_api_v1_auth_login_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MobileLoginRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_LoginResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_me_api_v1_auth_me_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + }; + }; + refresh_token_api_v1_auth_refresh_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + }; + }; + generate_script_api_v1_script_generate_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GenerateScriptRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_list_ScriptShot__"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + generate_script_stream_api_v1_script_generate_stream_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GenerateScriptRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + polish_content_api_v1_script_polish_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["app__schemas__script__PolishRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_str_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + check_model_health_api_v1_script_model_health_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_ModelHealthResponse_"]; + }; + }; + }; + }; + test_model_api_v1_script_test_model_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TestModelRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_TestModelResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_platforms_api_v1_ai_platforms_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_list_PlatformResponse__"]; + }; + }; + }; + }; + list_models_api_v1_ai_models_get: { + parameters: { + query?: { + capability?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_list_ModelResponse__"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + generate_text_api_v1_ai_generate_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GenerateRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_GenerateResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + health_check_api_v1_ai_health_get: { + parameters: { + query?: { + model_id?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_HealthResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + test_platform_connection_api_v1_ai_platforms__platform_id__test_get: { + parameters: { + query?: never; + header?: never; + path: { + platform_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + reload_config_api_v1_ai_reload_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + }; + }; + get_prompt_templates_api_v1_ai_prompts_templates_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_PromptTemplatesResponse_"]; + }; + }; + }; + }; + build_system_prompt_api_v1_ai_prompts_build_post: { + parameters: { + query?: { + duration?: number; + script_type?: string; + video_style?: string; + tone?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + generate_script_api_v1_ai_scripts_generate_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ScriptGenerateRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_ScriptGenerateResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + polish_script_content_api_v1_ai_scripts_polish_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["app__api__v1__ai_models__PolishRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_PolishResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_omni_video_tasks_api_v1_klingai_videos_omni_get: { + parameters: { + query?: { + page?: number; + page_size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_omni_video_api_v1_klingai_videos_omni_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["OmniVideoRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["app__schemas__common__ApiResponse_VideoGenerateResponse___1"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_omni_video_task_api_v1_klingai_videos_omni__task_id__get: { + parameters: { + query?: never; + header?: never; + path: { + task_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_TaskStatusResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_image_to_video_api_v1_klingai_videos_image2video_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Image2VideoRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["app__schemas__common__ApiResponse_VideoGenerateResponse___1"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + extend_video_api_v1_klingai_videos_extend_post: { + parameters: { + query: { + video_id: string; + prompt?: string | null; + duration?: number; + callback_url?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["app__schemas__common__ApiResponse_VideoGenerateResponse___1"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + identify_face_api_v1_klingai_videos_identify_face_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IdentifyFaceRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_IdentifyFaceResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_advanced_lip_sync_api_v1_klingai_videos_advanced_lip_sync_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AdvancedLipSyncRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["app__schemas__common__ApiResponse_VideoGenerateResponse___1"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_advanced_lip_sync_task_api_v1_klingai_videos_advanced_lip_sync__taskId__get: { + parameters: { + query?: never; + header?: never; + path: { + taskId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_TaskStatusResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_omni_image_api_v1_klingai_images_omni_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["OmniImageRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["app__schemas__common__ApiResponse_VideoGenerateResponse___1"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_omni_image_task_api_v1_klingai_images_omni__task_id__get: { + parameters: { + query?: never; + header?: never; + path: { + task_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_TaskStatusResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_image_api_v1_klingai_images_generations_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ImageGenerateRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["app__schemas__common__ApiResponse_VideoGenerateResponse___1"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_virtual_tryon_api_v1_klingai_virtual_tryon_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["VirtualTryonRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["app__schemas__common__ApiResponse_VideoGenerateResponse___1"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_taskStatus_api_v1_klingai_tasks__taskId__get: { + parameters: { + query?: { + task_type?: string; + }; + header?: never; + path: { + taskId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_TaskStatusResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_tasks_api_v1_klingai_tasks_get: { + parameters: { + query?: { + task_type?: string; + page?: number; + page_size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_elements_api_v1_klingai_elements_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_list_ElementResponse__"]; + }; + }; + }; + }; + create_element_api_v1_klingai_elements_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateElementRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_ElementResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_element_api_v1_klingai_elements__elementId__get: { + parameters: { + query?: never; + header?: never; + path: { + elementId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_ElementResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_element_api_v1_klingai_elements__elementId__delete: { + parameters: { + query?: never; + header?: never; + path: { + elementId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + ai_multiShot_api_v1_klingai_elements_ai_multi_shot_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AiMultiShotRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_AiMultiShotResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_ai_multiShot_task_api_v1_klingai_elements_ai_multi_shot__taskId__get: { + parameters: { + query?: never; + header?: never; + path: { + taskId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_custom_voices_api_v1_klingai_voices_custom_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_list_VoiceInfo__"]; + }; + }; + }; + }; + create_custom_voice_api_v1_klingai_voices_custom_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateCustomVoiceRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_CreateCustomVoiceResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_custom_voice_api_v1_klingai_voices_custom__voiceId__get: { + parameters: { + query?: never; + header?: never; + path: { + voiceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_custom_voice_api_v1_klingai_voices_custom__voiceId__delete: { + parameters: { + query?: never; + header?: never; + path: { + voiceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_preset_voices_api_v1_klingai_voices_presets_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_list_VoiceInfo__"]; + }; + }; + }; + }; + get_upload_token_api_v1_qiniu_upload_token_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UploadTokenRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_UploadTokenResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + upload_audio_api_v1_qiniu_upload_audio_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_upload_audio_api_v1_qiniu_upload_audio_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_FileUploadResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + upload_video_api_v1_qiniu_upload_video_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_upload_video_api_v1_qiniu_upload_video_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_FileUploadResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + upload_avatar_api_v1_qiniu_upload_avatar_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_upload_avatar_api_v1_qiniu_upload_avatar_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_FileUploadResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_file_info_api_v1_qiniu_files__key__get: { + parameters: { + query?: never; + header?: never; + path: { + key: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_file_api_v1_qiniu_files__key__delete: { + parameters: { + query?: never; + header?: never; + path: { + key: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + refresh_cdn_api_v1_qiniu_refresh_cdn_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": string[]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_video_generation_api_v1_video_generate_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["VideoGenerateRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["app__schemas__common__ApiResponse_VideoGenerateResponse___2"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_video_job_api_v1_video_jobs__job_id__get: { + parameters: { + query?: never; + header?: never; + path: { + job_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_VideoJobDetail_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + stream_video_job_api_v1_video_jobs__job_id__stream_get: { + parameters: { + query?: never; + header?: never; + path: { + job_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_video_job_status_api_v1_video_jobs__job_id__status_get: { + parameters: { + query?: never; + header?: never; + path: { + job_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_VideoJobStatus_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_digital_humans_api_v1_video_library_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_list_DigitalHuman__"]; + }; + }; + }; + }; + upload_video_api_v1_video_upload_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_upload_video_api_v1_video_upload_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_DigitalHuman_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + download_video_api_v1_video__video_id__download_get: { + parameters: { + query?: never; + header?: never; + path: { + video_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_video_thumbnail_api_v1_video__video_id__thumbnail_get: { + parameters: { + query?: never; + header?: never; + path: { + video_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + clone_avatar_api_v1_avatar_clone_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CloneAvatarRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_CloneAvatarResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_avatar_task_status_api_v1_avatar_tasks__task_id__get: { + parameters: { + query?: never; + header?: never; + path: { + task_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_AvatarTaskStatusResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + sse_avatar_clone_api_v1_avatar_clone_stream_get: { + parameters: { + query: { + /** @description 任务 ID */ + task_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + retry_avatar_task_api_v1_avatar_tasks__task_id__retry_post: { + parameters: { + query?: never; + header?: never; + path: { + task_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_avatar_api_v1_avatar__avatar_id__delete: { + parameters: { + query?: { + voice_id?: string | null; + }; + header?: never; + path: { + avatar_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_avatar_name_api_v1_avatar__avatar_id__patch: { + parameters: { + query?: never; + header?: never; + path: { + avatar_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateAvatarNameRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_avatar_library_api_v1_avatar_library_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_list_AvatarItem__"]; + }; + }; + }; + }; + get_avatar_health_api_v1_avatar_health_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_AvatarHealthResponse_"]; + }; + }; + }; + }; + admin_trigger_recovery_api_v1_avatar_admin_trigger_recovery_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + }; + }; + system_health_api_v1_system_health_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + }; + }; + system_version_api_v1_system_version_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + }; + }; + submit_caption_task_api_v1_caption_submit_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CaptionSubmitRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_CaptionTaskResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + query_caption_task_api_v1_caption_query__task_id__get: { + parameters: { + query?: { + blocking?: boolean; + }; + header?: never; + path: { + task_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_CaptionResult_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + generate_caption_api_v1_caption_generate_post: { + parameters: { + query?: { + max_wait_time?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CaptionSubmitRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_CaptionResult_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + generate_ass_api_v1_caption_generate_ass_post: { + parameters: { + query?: { + video_width?: number; + video_height?: number; + max_wait_time?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CaptionSubmitRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + generate_srt_api_v1_caption_generate_srt_post: { + parameters: { + query?: { + max_wait_time?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CaptionSubmitRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_SrtSubtitleResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + submit_auto_align_task_api_v1_caption_ata_submit_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AutoAlignSubmitRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_CaptionTaskResponse_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + query_auto_align_task_api_v1_caption_ata_query__task_id__get: { + parameters: { + query?: { + blocking?: boolean; + }; + header?: never; + path: { + task_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_AutoAlignResult_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + auto_align_caption_api_v1_caption_ata_align_post: { + parameters: { + query?: { + max_wait_time?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AutoAlignSubmitRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + convert_to_ass_api_v1_caption_convert_ass_post: { + parameters: { + query?: { + video_width?: number; + video_height?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CaptionResult-Input"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + convert_to_srt_api_v1_caption_convert_srt_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CaptionResult-Input"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + convert_to_vtt_api_v1_caption_convert_vtt_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CaptionResult-Input"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponse_dict_"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_task_api_v1_tasks__task_type__post: { + parameters: { + query?: never; + header?: never; + path: { + task_type: "video" | "image" | "script" | "subtitle" | "copy" | "avatar_clone"; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TaskCreateRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskCreateResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_task_status_api_v1_tasks__task_id__get: { + parameters: { + query?: never; + header?: never; + path: { + task_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["app__api__v1__tasks__TaskStatusResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_task_result_api_v1_tasks__task_id__result_get: { + parameters: { + query?: never; + header?: never; + path: { + task_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": Record; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + health_check_health_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + root__get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; +} diff --git a/tauri-app/src/api/modules/avatar.ts b/tauri-app/src/api/modules/avatar.ts new file mode 100644 index 0000000..3cb8bf2 --- /dev/null +++ b/tauri-app/src/api/modules/avatar.ts @@ -0,0 +1,250 @@ +import { invoke } from '@tauri-apps/api/core'; +import { client, PYTHON_API_BASE_URL } from '../client'; + +/** + * 从 Tauri 文件存储加载 token + */ +async function loadAuthToken(): Promise { + try { + const result = await invoke<{ code: number; data?: { token?: string } }>('load_auth_state'); + if (result.code === 200 && result.data?.token) { + return result.data.token; + } + } catch (e) { + // 回退到 localStorage + const legacy = localStorage.getItem('ai-video-auth'); + if (legacy) { + try { + const parsed = JSON.parse(legacy); + return parsed?.state?.token || null; + } catch { + // ignore + } + } + } + return null; +} + +/** + * 形象(Avatar)数据模型 + * 对应 KlingAI 的 "主体 + 自定义音色" 组合 + */ +export interface AvatarItem { + /** 本地形象唯一标识(如 avt_xxx,与 Kling elementId 不同) */ + id: string; + /** 展示名称 */ + name: string; + /** Kling 自定义音色 ID */ + voiceId: string; + /** Kling 主体 ID(调用 omni-video API 时使用) */ + elementId: number; + /** 原始人物视频 URL */ + videoUrl: string; + /** 音色试听 URL */ + trialUrl?: string; + /** 创建时间 */ + recordTime: string; +} + +/** + * 形象上传及克隆参数 + * + * API 统一使用 camelCase 与后端保持一致 + */ +export interface CloneAvatarParams { + name: string; + videoUrl: string; +} + +/** + * 形象克隆响应 + */ +export interface CloneAvatarResult { + id: string; + name: string; + voiceId: string; + elementId: number; + videoUrl: string; + trialUrl?: string; + message: string; +} + +/** + * 音频生成进度事件 + */ +export interface AvatarGenerationEvent { + type: 'start' | 'preparing' | 'processing' | 'completed' | 'error'; + progress: number; + message: string; + voiceId?: string; + elementId?: number; + trialUrl?: string; +} + +/** + * 形象分类 + */ +export const AVATAR_CATEGORIES = [ + { key: 'clone', label: '克隆形象' }, + { key: 'preset', label: '系统预设' }, +]; + +/** + * 形象相关 API + */ +export const avatarApi = { + /** + * 创建形象克隆(串行:音色 → 主体 → 绑定) + * POST /avatar/clone + */ + cloneAvatar: async (params: CloneAvatarParams): Promise<{ taskId: string; status: string }> => { + return client.post<{ taskId: string; status: string }>('/avatar/clone', params); + }, + + /** + * 查询形象克隆任务状态 + * GET /avatar/tasks/:taskId + */ + getAvatarTask: async ( + taskId: string + ): Promise<{ + taskId: string; + status: string; + failReason?: string; + voiceId?: string; + elementId?: number; + trialUrl?: string; + videoUrl: string; + name: string; + createdAt: string; + updatedAt: string; + }> => { + return client.get(`/avatar/tasks/${taskId}`); + }, + + /** + * 删除形象 + * DELETE /avatar/:id + */ + deleteAvatar: async (avatarId: string, voiceId?: string): Promise => { + const query = voiceId ? `?voiceId=${encodeURIComponent(voiceId)}` : ''; + return client.delete(`/avatar/${avatarId}${query}`); + }, + + /** + * 更新形象名称 + * PATCH /avatar/{avatarId} + */ + updateAvatarName: async (avatarId: string, params: { name: string }): Promise => { + return client.patch(`/avatar/${avatarId}`, params); + }, + + /** + * 获取系统预设形象库 + * GET /avatar/presets + * + * TODO: 后端对接完成后启用 + */ + getPresetLibrary: async (): Promise => { + // return client.get("/avatar/presets"); + return []; + }, + + /** + * 获取后端克隆形象库 + * GET /avatar/library + */ + getAvatarLibrary: async (): Promise => { + return client.get('/avatar/library'); + }, + + /** + * 上传形象视频到七牛云 + * POST /qiniu/upload/avatar + */ + uploadAvatarVideo: async ( + file: File, + name?: string, + fileHash?: string + ): Promise<{ url: string; key: string; isDuplicate?: boolean; existingTaskId?: string }> => { + const formData = new FormData(); + formData.append('file', file); + if (name) { + formData.append('name', name); + } + if (fileHash) { + formData.append('fileHash', fileHash); + } + return client.postForm<{ url: string; key: string; isDuplicate?: boolean; existingTaskId?: string }>( + '/qiniu/upload/avatar', + formData + ); + }, + + /** + * SSE 流:实时跟踪形象克隆任务进度 + * GET /avatar/clone/stream + */ + cloneAvatarStream: async ( + taskId: string, + onEvent: (event: { + taskId: string; + status: string; + failReason?: string; + voiceId?: string; + elementId?: number; + trialUrl?: string; + videoUrl: string; + name: string; + }) => void, + onError?: (error: Error) => void + ): Promise<{ close: () => void }> => { + const API_BASE = import.meta.env.VITE_API_BASE_URL || PYTHON_API_BASE_URL; + const controller = new AbortController(); + + // 从 Tauri 文件存储读取 token + const token = await loadAuthToken(); + const headers: Record = { Accept: 'text/event-stream' }; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + fetch(`${API_BASE}/avatar/clone/stream?taskId=${encodeURIComponent(taskId)}`, { + headers, + signal: controller.signal, + }) + .then(async response => { + if (!response.ok) { + throw new Error(`SSE error: ${response.status}`); + } + const reader = response.body?.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + while (true) { + const { done, value } = await reader!.read(); + if (done) { + break; + } + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; + for (const line of lines) { + if (line.startsWith('data: ')) { + const dataStr = line.slice(6); + if (dataStr === '[DONE]') { + continue; + } + try { + onEvent(JSON.parse(dataStr)); + } catch (e) { + console.error('Parse SSE data error:', dataStr); + } + } + } + } + }) + .catch(error => onError?.(error as Error)); + + return { close: () => controller.abort() }; + }, +}; diff --git a/tauri-app/src/api/modules/localStorage.ts b/tauri-app/src/api/modules/localStorage.ts new file mode 100644 index 0000000..57c1d33 --- /dev/null +++ b/tauri-app/src/api/modules/localStorage.ts @@ -0,0 +1,306 @@ +/** + * 本地文件存储 API + * ================= + * + * 通过 Tauri 与 Rust 后端通信,操作本地文件系统 + * 存储位置: ~/Documents/Meijiaka/ + */ + +import { invoke } from '@tauri-apps/api/core'; +import { ApiResponse } from '../types'; + +// 检查是否在 Tauri 环境中(Tauri 2.x 兼容) +const isTauri = (): boolean => { + if (typeof window === 'undefined') return false; + // Tauri 2.x 使用 __TAURI_INTERNALS__ 或 __TAURI__ + return !!(window as any).__TAURI_INTERNALS__ || !!(window as any).__TAURI__; +}; + +// 安全调用 Tauri 命令 +const safeInvoke = async (cmd: string, args?: any): Promise => { + const tauriAvailable = isTauri(); + console.log(`[localStorage] Command: ${cmd}, Tauri available: ${tauriAvailable}`); + + if (!tauriAvailable) { + console.warn(`[localStorage] Tauri not available, command: ${cmd} skipped`); + return null; + } + + // Tauri 环境:真实调用 + try { + console.log(`[localStorage] Invoking ${cmd} with args:`, args); + const result = await invoke(cmd, args); + console.log(`[localStorage] ${cmd} result:`, result); + return result; + } catch (e) { + console.error(`[localStorage] invoke ${cmd} failed:`, e); + throw e; + } +}; + +/** + * 项目元数据接口 + */ +export interface ProjectMeta { + id: string; + title: string; + topic?: string; + status: 'draft' | 'published'; + currentStep: number; // 当前步骤:1=脚本生成, 2=视频生成, 3=字幕压制, 4=封面制作, 5=视频合成 + createdAt: number; + updatedAt: number; + coverPath?: string; + finalVideoPath?: string; + exportedAt?: number; // 最终视频合成成功的时间戳 + selectedHumanId?: string; // 选中的形象 humanId + selectedElementId?: number; // 选中的形象 elementId(Kling) + selectedVoiceId?: string; // 选中的形象 voiceId + coverConfig?: { + caption?: string; + coverStyle?: { + fontSize: number; + color: string; + strokeColor: string; + strokeWidth: number; + }; + selectedPreset?: string; + bgSource?: 'first-frame' | 'ai-generate'; + }; + scriptDuration?: number; // 脚本生成时的视频时长(秒) + scriptType?: string; // 脚本生成时的脚本类型 +} + +/** + * 字幕打轴结果 + */ +export interface AlignmentResult { + status: 'pending' | 'aligning' | 'completed' | 'failed'; + utterances?: Array<{ + text: string; + start_time: number; + end_time: number; + }>; + duration?: number; + errorMessage?: string; +} + +/** + * 分镜数据接口 + */ +export interface ProjectSegment { + id: number; + type: 'segment' | 'empty_shot'; + scene?: string; + voiceover?: string; + duration: string; + videoPath?: string; // 本地视频文件路径 + videoUrl?: string; // 七牛云视频 URL(用于字幕生成等后续处理) + elementId?: number; // 分镜使用的形象ID + voiceId?: string; // 空镜使用的音色ID + alignmentResult?: AlignmentResult; // 字幕打轴结果 + burnedVideoPath?: string; // 压制字幕后的视频路径 + burnedAt?: number; // 压制字幕的时间戳 +} + +/** + * 本地项目存储 API + */ +export const localProjectApi = { + /** + * 保存项目元数据 + */ + saveMeta: async (projectId: string, meta: ProjectMeta): Promise => { + // 按指定字段顺序序列化 + const orderedMeta = { + id: meta.id, + title: meta.title, + topic: meta.topic, + status: meta.status, + currentStep: meta.currentStep, + createdAt: meta.createdAt, + updatedAt: meta.updatedAt, + coverPath: meta.coverPath, + finalVideoPath: meta.finalVideoPath, + exportedAt: meta.exportedAt, + selectedHumanId: meta.selectedHumanId, + selectedElementId: meta.selectedElementId, + selectedVoiceId: meta.selectedVoiceId, + coverConfig: meta.coverConfig, + scriptDuration: meta.scriptDuration, + scriptType: meta.scriptType, + }; + const jsonContent = JSON.stringify(orderedMeta, null, 2); + const res = await safeInvoke>('save_project_meta_raw', { + projectId: projectId, + jsonContent + }); + return res?.code === 200; + }, + + /** + * 加载项目元数据 + */ + loadMeta: async (projectId: string): Promise => { + const res = await safeInvoke>('load_project_meta', { projectId: projectId }); + if (res?.code === 200 && res.data) { + return res.data; + } + return null; + }, + + /** + * 保存分镜数据 + */ + saveSegments: async (projectId: string, segments: ProjectSegment[]): Promise => { + // 按指定字段顺序序列化每个分镜 + const orderedSegments = segments.map(s => ({ + id: s.id, + type: s.type, + scene: s.scene, + voiceover: s.voiceover, + duration: s.duration, + videoPath: s.videoPath, + videoUrl: s.videoUrl, + elementId: s.elementId, + voiceId: s.voiceId, + alignmentResult: s.alignmentResult, + burnedVideoPath: s.burnedVideoPath, + burnedAt: s.burnedAt, + })); + const jsonContent = JSON.stringify(orderedSegments, null, 2); + const res = await safeInvoke>('save_project_segments_raw', { + projectId: projectId, + jsonContent + }); + return res?.code === 200; + }, + + /** + * 加载分镜数据 + */ + loadSegments: async (projectId: string): Promise => { + const res = await safeInvoke>('load_project_segments', { projectId: projectId }); + if (res?.code === 200 && res.data) { + return res.data; + } + return []; + }, + + /** + * 列出所有本地项目 + */ + listProjects: async (): Promise => { + const res = await safeInvoke>('list_local_projects'); + if (res?.code === 200 && res.data) { + return res.data; + } + return []; + }, + + /** + * 删除本地项目 + */ + deleteProject: async (projectId: string): Promise => { + const res = await safeInvoke>('delete_local_project', { projectId: projectId }); + return res?.code === 200; + }, + + /** + * 保存完整项目(元数据 + 分镜) + */ + saveProject: async ( + projectId: string, + meta: ProjectMeta, + segments: ProjectSegment[] + ): Promise => { + const [metaRes, segmentsRes] = await Promise.all([ + localProjectApi.saveMeta(projectId, meta), + localProjectApi.saveSegments(projectId, segments) + ]); + return metaRes && segmentsRes; + }, + + /** + * 加载完整项目 + */ + loadProject: async (projectId: string): Promise<{ meta: ProjectMeta | null; segments: ProjectSegment[] }> => { + const [meta, segments] = await Promise.all([ + localProjectApi.loadMeta(projectId), + localProjectApi.loadSegments(projectId) + ]); + return { meta, segments }; + }, +}; + +/** + * 生成项目 ID + */ +export function generateProjectId(): string { + return `proj_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; +} + +/** + * 获取当前项目 ID(从 store 或生成新的) + */ +export function getCurrentProjectId(): string { + // 可以从 URL 参数、store 或 localStorage 获取 + // 这里简化处理,实际使用时需要更复杂的逻辑 + const stored = localStorage.getItem('current-project-id'); + if (stored) { + return stored; + } + const newId = generateProjectId(); + localStorage.setItem('current-project-id', newId); + return newId; +} + +/** + * 设置当前项目 ID + */ +export function setCurrentProjectId(projectId: string): void { + localStorage.setItem('current-project-id', projectId); +} + +/** + * 保存 Blob 到项目 assets 目录 + * 返回最终本地文件路径 + */ +export async function saveBlobToProject( + projectId: string, + blob: Blob, + filename: string +): Promise { + // 先将 Blob 转为 base64 + const reader = new FileReader(); + const base64Data = await new Promise((resolve, reject) => { + reader.onloadend = () => { + const result = reader.result as string; + // 去掉 data:image/png;base64, 前缀 + const data = result.split(',')[1]; + resolve(data); + }; + reader.onerror = reject; + reader.readAsDataURL(blob); + }); + + // 调用 Tauri 命令保存到本地 + const res = await safeInvoke>('save_project_asset', { + projectId, + filename, + base64Data, + }); + + if (!res || res.code !== 200 || !res.data) { + throw new Error('保存文件失败'); + } + + return res.data; +} + +/** + * 将远程图片 URL 转为 Blob + */ +export async function convertImageUrlToBlob(url: string): Promise { + const response = await fetch(url); + return await response.blob(); +} diff --git a/tauri-app/src/api/modules/project.ts b/tauri-app/src/api/modules/project.ts new file mode 100644 index 0000000..1196b7f --- /dev/null +++ b/tauri-app/src/api/modules/project.ts @@ -0,0 +1,130 @@ +import { client } from '../client'; +import type { ScriptShot } from '../types'; + +export type { ScriptShot }; + +/** + * 项目类型 + */ +export interface Project { + id: string; + userId: string; + title: string | null; + topic: string | null; + scriptType: string | null; + duration: number | null; + globalAudioSettings: Record | null; + status: 'draft' | 'editing' | 'completed'; + createdAt: string; + updatedAt: string; +} + +/** + * 创建项目请求 + */ +export interface CreateProjectParams { + title?: string; + topic?: string; + scriptType?: string; + duration?: number; + globalAudioSettings?: Record; + status?: 'draft' | 'editing' | 'completed'; +} + +/** + * 更新项目请求 + */ +export interface UpdateProjectParams { + title?: string; + topic?: string; + scriptType?: string; + duration?: number; + globalAudioSettings?: Record; + status?: 'draft' | 'editing' | 'completed'; +} + +/** + * 保存分镜请求 + */ +export interface SaveSegmentsParams { + segments: Array<{ + sequence: number; + type: 'segment' | 'empty_shot'; + scene?: string; + prompt?: string; + voiceover?: string; + duration?: string; + }>; +} + +/** + * 项目列表响应 + */ +export interface ProjectListResponse { + total: number; + items: Project[]; +} + +/** + * 项目 API + */ +export const projectApi = { + /** + * 创建项目 + * POST /projects + */ + create: async (params: CreateProjectParams): Promise => { + return client.post('/projects', params); + }, + + /** + * 获取项目列表 + * GET /projects + */ + list: async (skip = 0, limit = 100): Promise => { + return client.get(`/projects?skip=${skip}&limit=${limit}`); + }, + + /** + * 获取项目详情(含分镜) + * GET /projects/{id} + */ + get: async (projectId: string): Promise => { + return client.get(`/projects/${projectId}`); + }, + + /** + * 更新项目 + * PUT /projects/{id} + */ + update: async (projectId: string, params: UpdateProjectParams): Promise => { + return client.put(`/projects/${projectId}`, params); + }, + + /** + * 删除项目 + * DELETE /projects/{id} + */ + delete: async (projectId: string): Promise<{ deleted: boolean }> => { + return client.delete<{ deleted: boolean }>(`/projects/${projectId}`); + }, + + /** + * 保存/替换项目分镜 + * PUT /projects/{id}/segments + */ + saveSegments: async ( + projectId: string, + segments: SaveSegmentsParams['segments'] + ): Promise => { + return client.put(`/projects/${projectId}/segments`, segments); + }, + + /** + * 获取项目分镜 + * GET /projects/{id}/segments + */ + getSegments: async (projectId: string): Promise => { + return client.get(`/projects/${projectId}/segments`); + }, +}; diff --git a/tauri-app/src/api/modules/script.ts b/tauri-app/src/api/modules/script.ts new file mode 100644 index 0000000..524b6ca --- /dev/null +++ b/tauri-app/src/api/modules/script.ts @@ -0,0 +1,110 @@ +import { client } from '../client'; +import type { ScriptShot } from '../types'; + +export type { ScriptShot }; + +/** + * 脚本生成请求参数 + */ +export interface GenerateScriptParams { + topic: string; + duration: number; // 秒 + type: string; // 脚本类型描述 +} + +/** + * 模型健康状态 + */ +export interface ModelHealth { + id: string; + name: string; + isAvailable: boolean; + responseTime: number; + lastError: string | null; +} + +/** + * 模型健康检查响应 + */ +export interface ModelHealthResponse { + status: 'healthy' | 'unhealthy' | 'error'; + models: ModelHealth[]; + recommendedModel: ModelHealth | null; + totalModels: number; + availableModels: number; + error?: string; +} + +/** + * 测试模型请求 + */ +export interface TestModelRequest { + modelId?: string; +} + +/** + * 测试模型响应 + */ +export interface TestModelResponse { + success: boolean; + model: string; + responseTime?: number; + error?: string; + checkedAt?: string; +} + +/** + * 脚本相关 API + */ +export const scriptApi = { + /** + * 生成脚本内容(同步) + * POST /script/generate + */ + generate: async (params: GenerateScriptParams): Promise => { + return client.post('/script/generate', { + topic: params.topic, + duration: params.duration, + scriptType: params.type, + }); + }, + + /** + * AI 润色脚本 + * POST /script/polish + * + * @param shotType 镜头类型:'segment'(分镜) 或 'empty_shot'(空镜),用于画面润色时区分 + */ + polish: async ( + _segmentId: number, + content: string, + polishType: 'scene' | 'prompt' | 'voiceover' = 'voiceover', + shotType: 'segment' | 'empty_shot' = 'segment' + ): Promise => { + const backendType = polishType === 'prompt' ? 'scene' : polishType; + return client.post('/script/polish', { + content: content, + polishType: backendType, + shotType: shotType, + }); + }, + + /** + * 检查模型健康状态 + * GET /script/model-health + */ + checkModelHealth: async (): Promise => { + return client.get('/script/model-health'); + }, + + /** + * 测试指定模型连接 + * POST /script/test-model?modelId=xxx + */ + testModel: async (modelId?: string): Promise => { + const path = modelId + ? `/script/test-model?modelId=${encodeURIComponent(modelId)}` + : '/script/test-model'; + return client.post(path); + }, +}; diff --git a/tauri-app/src/api/modules/task.ts b/tauri-app/src/api/modules/task.ts new file mode 100644 index 0000000..073a0fe --- /dev/null +++ b/tauri-app/src/api/modules/task.ts @@ -0,0 +1,56 @@ +/** + * 任务管理 API + * ============ + * + * 提供任务列表查询接口。 + * 任务状态以后端 Redis 为唯一真相源,前端不持久化。 + */ + +import { client } from '../client'; +import type { TaskType, TaskStatus } from '../../store/taskStore'; + +export interface TaskItem { + taskId: string; + type: TaskType; + status: TaskStatus; + progress: number; + message: string; + completed: number; + total: number; + result?: unknown; + error?: string; +} + +/** + * 查询当前用户进行中的任务列表 + * @param projectId 可选,按项目过滤 + */ +export async function listTasks(projectId?: string): Promise { + const query = projectId ? `?project_id=${encodeURIComponent(projectId)}` : ''; + const data = await client.get< + Array<{ + task_id: string; + type: TaskType; + status: TaskStatus; + progress: number; + message: string; + completed: number; + total: number; + result?: unknown; + error?: string; + }> + >(`/tasks${query}`); + + // snake_case → camelCase 转换 + return data.map((item) => ({ + taskId: item.task_id, + type: item.type, + status: item.status, + progress: item.progress, + message: item.message, + completed: item.completed, + total: item.total, + result: item.result, + error: item.error, + })); +} diff --git a/tauri-app/src/api/modules/videoComposite.ts b/tauri-app/src/api/modules/videoComposite.ts new file mode 100644 index 0000000..83d3a98 --- /dev/null +++ b/tauri-app/src/api/modules/videoComposite.ts @@ -0,0 +1,28 @@ +import { invoke } from '@tauri-apps/api/core'; +import { ApiResponse } from '../types'; + +export interface VideoCompositeRequest { + video_paths: string[]; + audio_path?: string; + cover_path?: string; + output_path: string; +} + +/** + * 视频合成 API(走 Tauri IPC 直接命令) + */ +export const compositeApi = { + /** + * 执行视频合成 + */ + synthesis: async (request: VideoCompositeRequest): Promise => { + const response = await invoke>('video_composite_synthesis', { request }); + if (!response || typeof response !== 'object') { + throw new Error('IPC request returned invalid response'); + } + if ('code' in response && response.code !== 200) { + throw new Error(response.message || '视频合成失败'); + } + return response.data as string; + }, +}; diff --git a/tauri-app/src/api/types.ts b/tauri-app/src/api/types.ts new file mode 100644 index 0000000..fb530b6 --- /dev/null +++ b/tauri-app/src/api/types.ts @@ -0,0 +1,58 @@ +/** + * 通用 API 响应格式 + */ +export interface ApiResponse { + code: number; + data: T; + message: string; +} + +/** + * 分页请求参数 + */ +export interface PageParams { + page: number; + pageSize: number; +} + +/** + * 分页响应包装 + */ +export interface PageResponse { + items: T[]; + total: number; + hasMore: boolean; +} + +/** + * 字幕打轴结果 + */ +export interface AlignmentResult { + status: 'pending' | 'aligning' | 'completed' | 'failed'; + utterances?: Array<{ + text: string; + start_time: number; + end_time: number; + }>; + duration?: number; + errorMessage?: string; +} + +/** + * 脚本镜头定义(分镜或空镜) + * 前后端统一的权威类型 + */ +export interface ScriptShot { + id: number; + type: 'segment' | 'empty_shot'; + scene?: string; // 画面描述 + voiceover: string; // 配音文案 + duration: string; + videoPath?: string; // 本地视频文件路径 + videoUrl?: string; // 七牛云视频 URL(用于字幕生成等后续处理) + elementId?: number; // 分镜使用的形象ID(Kling element_id) + voiceId?: string; // 空镜使用的音色ID + alignmentResult?: AlignmentResult; // 字幕打轴结果 + burnedVideoPath?: string; // 压制字幕后的视频路径 + burnedAt?: number; // 压制字幕的时间戳 +} diff --git a/tauri-app/src/assets/react.svg b/tauri-app/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/tauri-app/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tauri-app/src/components/ErrorBoundary/ErrorBoundary.css b/tauri-app/src/components/ErrorBoundary/ErrorBoundary.css new file mode 100644 index 0000000..aacd127 --- /dev/null +++ b/tauri-app/src/components/ErrorBoundary/ErrorBoundary.css @@ -0,0 +1,65 @@ +/** + * ErrorBoundary 样式 + */ + +.error-boundary { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: var(--spacing-2xl); + text-align: center; + min-height: 300px; +} + +.error-boundary-icon { + width: 80px; + height: 80px; + border-radius: 50%; + background: var(--error-light, rgb(239 68 68 / 10%)); + color: var(--error); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: var(--spacing-lg); +} + +.error-boundary-title { + font-size: var(--font-lg); + font-weight: 600; + color: var(--text-primary); + margin: 0 0 var(--spacing-sm) 0; +} + +.error-boundary-message { + font-size: var(--font-base); + color: var(--text-secondary); + margin: 0 0 var(--spacing-xl) 0; + max-width: 400px; +} + +.error-boundary-details { + width: 100%; + max-width: 600px; + margin-bottom: var(--spacing-lg); + text-align: left; +} + +.error-boundary-details summary { + font-size: var(--font-sm); + color: var(--text-tertiary); + cursor: pointer; + user-select: none; +} + +.error-boundary-stack { + background: var(--bg-tertiary); + border-radius: var(--radius-md); + padding: var(--spacing-md); + font-size: var(--font-sm); + font-family: var(--font-mono, monospace); + color: var(--text-secondary); + overflow: auto; + margin-top: var(--spacing-sm); + max-height: 200px; +} diff --git a/tauri-app/src/components/ErrorBoundary/ErrorBoundary.tsx b/tauri-app/src/components/ErrorBoundary/ErrorBoundary.tsx new file mode 100644 index 0000000..fb8482f --- /dev/null +++ b/tauri-app/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -0,0 +1,128 @@ +/** + * Error Boundary 组件 + * =================== + * + * 捕获 React 组件树中的错误,防止整个应用崩溃 + * 支持错误上报和优雅降级 + */ + +import React, { Component, ErrorInfo, ReactNode } from 'react'; +import './ErrorBoundary.css'; + +interface Props { + children: ReactNode; + fallback?: ReactNode; + onError?: (error: Error, errorInfo: ErrorInfo) => void; +} + +interface State { + hasError: boolean; + error: Error | null; + errorInfo: ErrorInfo | null; +} + +/** + * 错误边界组件 + * + * 使用方式: + * + * + * + * + * 或者使用自定义 fallback: + * }> + * + * + */ +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false, error: null, errorInfo: null }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error, errorInfo: null }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo): void { + this.setState({ errorInfo }); + + // 调用外部错误处理回调 + this.props.onError?.(error, errorInfo); + + // 控制台输出 + console.error('[ErrorBoundary] Caught error:', error); + console.error('[ErrorBoundary] Component stack:', errorInfo.componentStack); + + // 这里可以添加错误上报逻辑,如 Sentry + // reportError(error, errorInfo); + } + + handleRetry = (): void => { + this.setState({ hasError: false, error: null, errorInfo: null }); + }; + + render(): ReactNode { + if (this.state.hasError) { + // 使用自定义 fallback + if (this.props.fallback) { + return this.props.fallback; + } + + // 默认错误页面 + return ( +
+
+ + + + + +
+

出错了

+

组件渲染时发生错误,请刷新页面或返回重试

+ {import.meta.env.DEV && this.state.error && ( +
+ 错误详情(仅开发环境) +
+                {this.state.error.toString()}
+                {'\n'}
+                {this.state.errorInfo?.componentStack}
+              
+
+ )} + +
+ ); + } + + return this.props.children; + } +} + +/** + * 页面级错误边界(包裹整个页面) + */ +export function withErrorBoundary

( + Component: React.ComponentType

, + fallback?: ReactNode +): React.FC

{ + return function WrappedComponent(props: P) { + return ( + + + + ); + }; +} + +export default ErrorBoundary; diff --git a/tauri-app/src/components/ErrorBoundary/index.ts b/tauri-app/src/components/ErrorBoundary/index.ts new file mode 100644 index 0000000..36795d7 --- /dev/null +++ b/tauri-app/src/components/ErrorBoundary/index.ts @@ -0,0 +1,2 @@ +export { ErrorBoundary, withErrorBoundary } from './ErrorBoundary'; +export { default } from './ErrorBoundary'; diff --git a/tauri-app/src/components/Layout/Sidebar.css b/tauri-app/src/components/Layout/Sidebar.css new file mode 100644 index 0000000..7357faa --- /dev/null +++ b/tauri-app/src/components/Layout/Sidebar.css @@ -0,0 +1,253 @@ +.sidebar { + width: var(--sidebar-width); + height: 100vh; + display: flex; + flex-direction: column; + background: var(--glass-bg); + backdrop-filter: var(--glass-blur); + backdrop-filter: var(--glass-blur); + border-right: 1px solid var(--border-light); + position: fixed; + left: 0; + top: 0; + z-index: 100; + transition: width var(--transition-normal); +} + +.sidebar-header { + padding: var(--spacing-xl) var(--spacing-sm); + border-bottom: 1px solid var(--border-light); +} + +.sidebar-logo { + display: flex; + align-items: center; + justify-content: center; + gap: var(--spacing-sm); +} + +.sidebar-title { + font-size: var(--font-lg); + font-weight: 700; + background: var(--primary-gradient); + background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.sidebar-nav { + flex: 1; + overflow-y: auto; + padding: var(--spacing-md) var(--spacing-sm); + display: flex; + flex-direction: column; + gap: var(--spacing-2xs); +} + +.nav-group { + display: flex; + flex-direction: column; +} + +.nav-item { + display: flex; + align-items: center; + gap: var(--spacing-md); + padding: var(--spacing-md) var(--spacing-md); + border-radius: var(--radius-md); + color: var(--text-secondary); + background: transparent; + border: none; + cursor: pointer; + font-size: var(--font-base); + font-family: var(--font-family); + width: 100%; + text-align: left; + transition: all var(--transition-fast); + position: relative; +} + +.nav-item:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.nav-item.active, +.nav-item.child-active { + background: var(--primary-light); + color: var(--primary); + font-weight: 500; +} + +.nav-icon { + flex-shrink: 0; +} + +.nav-label { + flex: 1; +} + +.nav-new-project { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: var(--radius-md); + color: rgba(74, 222, 128, 0.9); + background: transparent; + border: none; + cursor: pointer; + padding: 0; + margin-left: auto; + margin-right: 0; + transform: translateY(1px); + transition: all 0.2s ease; +} + +.nav-new-project:hover { + transform: translateY(1px) scale(1.08); +} + +.nav-new-project:active { + transform: translateY(1px) scale(0.96); +} + +.nav-chevron { + flex-shrink: 0; + transition: transform var(--transition-fast); +} + +.nav-chevron.expanded { + transform: rotate(90deg); +} + +.nav-children { + display: flex; + flex-direction: column; + padding-left: 4px; /* Reduced indentation as requested */ + gap: var(--spacing-2xs); + animation: fadeIn 0.2s ease; +} + +.nav-child-item { + display: flex; + align-items: center; + gap: var(--spacing-md); + padding: var(--spacing-sm) var(--spacing-md); + border-radius: var(--radius-md); + color: var(--text-secondary); + background: transparent; + border: none; + cursor: pointer; + font-size: var(--font-sm); + font-family: var(--font-family); + width: 100%; + text-align: left; + transition: all var(--transition-fast); +} + +.nav-child-item:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.nav-child-item.active { + color: var(--primary); + font-weight: 500; +} + +.sidebar-footer { + padding: var(--spacing-md); + border-top: 1px solid var(--border-light); + display: flex; + flex-direction: column; + gap: var(--spacing-xs); +} + +.sidebar-user { + display: flex; + align-items: center; + justify-content: center; + gap: var(--spacing-sm); + padding: var(--spacing-sm); + border-radius: var(--radius-md); + cursor: pointer; + transition: background var(--transition-fast); +} + +.sidebar-user:hover { + background: var(--bg-hover); +} + +.sidebar-divider { + height: 1px; + background: var(--border-light); + margin: var(--spacing-xs) 0; +} + +.user-avatar { + width: 36px; + height: 36px; + border-radius: var(--radius-full); + background: var(--primary-gradient); + color: var(--text-inverse); + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: var(--font-sm); + flex-shrink: 0; +} + +.user-info { + display: flex; + flex-direction: column; + min-width: 0; +} + +.user-name { + font-size: var(--font-sm); + font-weight: 500; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.user-role { + font-size: var(--font-xs); + color: var(--text-tertiary); +} + +.sidebar-logout { + display: flex; + align-items: center; + justify-content: center; + gap: var(--spacing-sm); + padding: var(--spacing-xs) var(--spacing-sm); + border-radius: var(--radius-md); + color: var(--text-tertiary); + background: transparent; + border: none; + cursor: pointer; + font-size: var(--font-xs); + font-family: var(--font-family); + width: 100%; + text-align: center; + transition: all var(--transition-fast); +} + +.sidebar-logout:hover { + background: var(--bg-hover); + color: var(--text-secondary); +} + +.sidebar-logout svg { + flex-shrink: 0; + opacity: 0.7; +} + +.sidebar-logout:hover svg { + opacity: 1; +} diff --git a/tauri-app/src/components/Layout/Sidebar.tsx b/tauri-app/src/components/Layout/Sidebar.tsx new file mode 100644 index 0000000..890d340 --- /dev/null +++ b/tauri-app/src/components/Layout/Sidebar.tsx @@ -0,0 +1,203 @@ +import { useState } from 'react'; +import { createNewProject } from '../../store'; +import './Sidebar.css'; + +interface NavItem { + id: string; + label: string; + icon: string; + children?: { id: string; label: string }[]; +} + +const navItems: NavItem[] = [ + { + id: 'video-creation', + label: '视频创作', + icon: 'M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z', + }, + { + id: 'content-management', + label: '内容管理', + icon: 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10', + children: [ + { id: 'avatar-clone', label: '形象克隆' }, + { id: 'my-works', label: '我的作品' }, + ], + }, + { + id: 'settings', + label: '系统设置', + icon: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z M15 12a3 3 0 11-6 0 3 3 0 016 0z', + children: [ + { id: 'theme-settings', label: '主题设置' }, + { id: 'about-us', label: '关于我们' }, + { id: 'system-update', label: '系统更新' }, + ], + }, +]; + +interface SidebarProps { + currentPath: string; + onNavigate: (path: string) => void; +} + +export default function Sidebar({ currentPath, onNavigate }: SidebarProps) { + const [expandedItems, setExpandedItems] = useState>( + new Set(['content-management', 'settings']) + ); + + const toggleExpand = (id: string) => { + setExpandedItems(prev => { + const next = new Set(prev); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } + return next; + }); + }; + + const handleClick = (item: NavItem) => { + if (item.children) { + toggleExpand(item.id); + // Navigate to first child + if (!expandedItems.has(item.id)) { + onNavigate(item.children[0].id); + } + } else { + onNavigate(item.id); + } + }; + + const handleNewProject = async (e: React.MouseEvent) => { + e.stopPropagation(); + await createNewProject(); + onNavigate('video-creation'); + }; + + const isActive = (id: string) => currentPath === id; + + return ( +

+ ); +} diff --git a/tauri-app/src/components/Modal/AvatarUploadModal.css b/tauri-app/src/components/Modal/AvatarUploadModal.css new file mode 100644 index 0000000..fcbbec0 --- /dev/null +++ b/tauri-app/src/components/Modal/AvatarUploadModal.css @@ -0,0 +1,115 @@ +/* ============================================ + 形象克隆上传弹窗 + ============================================ */ + +.clone-upload-modal { + display: flex; + flex-direction: column; + gap: var(--spacing-xl); +} + +.clone-upload-form { + display: flex; + flex-direction: column; + gap: var(--spacing-lg); +} + +.form-field { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.form-field label { + font-size: var(--font-sm); + font-weight: 500; + color: var(--text-primary); +} + +.form-field input[type='text'] { + padding: var(--spacing-sm) var(--spacing-md); + border: 1px solid var(--border-light); + border-radius: var(--radius-md); + font-size: var(--font-sm); + transition: border-color var(--transition-fast); +} + +.form-field input[type='text']:focus { + outline: none; + border-color: var(--primary); +} + +/* 文件拖放区域 */ +.file-drop-zone { + padding: var(--spacing-xl); + border: 2px dashed var(--border-color); + border-radius: var(--radius-lg); + background: var(--bg-input); + cursor: pointer; + transition: all var(--transition-fast); + text-align: center; +} + +.file-drop-zone:hover { + border-color: var(--primary); + background: var(--bg-hover); +} + +.file-drop-zone.has-file { + border-color: var(--success); + background: rgb(34 197 94 / 5%); +} + +.file-placeholder { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--spacing-sm); + color: var(--text-secondary); +} + +.file-placeholder svg { + color: var(--text-tertiary); +} + +.file-hint { + font-size: var(--font-xs); + color: var(--text-tertiary); + text-align: center; + line-height: 1.6; +} + +/* 已选文件信息 */ +.file-info { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--spacing-xs); +} + +.file-info svg { + color: var(--success); +} + +.file-name { + font-size: var(--font-sm); + font-weight: 500; + color: var(--text-primary); + word-break: break-all; +} + +.file-size { + font-size: var(--font-xs); + color: var(--text-tertiary); +} + +/* 弹窗底部按钮 */ +.clone-upload-actions { + display: flex; + justify-content: flex-end; + gap: var(--spacing-md); + padding-top: var(--spacing-md); + border-top: 1px solid var(--border-light); +} + +/* 处理中状态 - 进度显示已由 ProgressModal.css 接管 */ diff --git a/tauri-app/src/components/Modal/AvatarUploadModal.tsx b/tauri-app/src/components/Modal/AvatarUploadModal.tsx new file mode 100644 index 0000000..9e6a321 --- /dev/null +++ b/tauri-app/src/components/Modal/AvatarUploadModal.tsx @@ -0,0 +1,470 @@ +import { useState, useRef, useEffect } from 'react'; +import type { ChangeEvent } from 'react'; +import Modal from './Modal'; +import { avatarApi, type AvatarItem } from '../../api/modules/avatar'; +import { calculateFileSHA256 } from '../../utils/fileHash'; +import { useTask } from '../../hooks/useTask'; +import { addClonedAvatarToLocal } from '../../utils/avatarStorage'; +import { toast } from '../../store/uiStore'; +import './AvatarUploadModal.css'; +import '../ProgressModal/ProgressModal.css'; + +interface AvatarUploadModalProps { + open: boolean; + onClose: () => void; + onSuccess: () => void; +} + +type UploadStep = 'idle' | 'uploading' | 'cloning' | 'completed' | 'error'; + +export default function AvatarUploadModal({ open, onClose, onSuccess }: AvatarUploadModalProps) { + const [avatarName, setAvatarName] = useState(''); + const [selectedFile, setSelectedFile] = useState(null); + const [currentStep, setCurrentStep] = useState('idle'); + const [statusText, setStatusText] = useState(''); + + const fileInputRef = useRef(null); + const isActiveRef = useRef(true); + const avatarNameRef = useRef(avatarName); + const completedRef = useRef(false); + + useEffect(() => { + avatarNameRef.current = avatarName; + }, [avatarName]); + + const { submit } = useTask(); + + const resetState = () => { + setAvatarName(''); + setSelectedFile(null); + setCurrentStep('idle'); + setStatusText(''); + }; + + const cleanup = () => { + isActiveRef.current = false; + }; + + useEffect(() => { + if (!open) { + // Modal 关闭时重置内部状态(避免 reopen 时显示旧数据) + // eslint-disable-next-line react-hooks/set-state-in-effect + resetState(); + } + return () => { + cleanup(); + }; + }, [open]); + + const validateVideoFile = (file: File): Promise<{ valid: boolean; error?: string }> => { + return new Promise(resolve => { + const allowedTypes = ['video/mp4', 'video/quicktime']; + const allowedExts = ['.mp4', '.mov']; + const ext = file.name.substring(file.name.lastIndexOf('.')).toLowerCase(); + + if (!allowedTypes.includes(file.type) && !allowedExts.includes(ext)) { + resolve({ valid: false, error: '仅支持 MP4、MOV 格式' }); + return; + } + + const maxSize = 200 * 1024 * 1024; + if (file.size > maxSize) { + resolve({ valid: false, error: '文件大小不能超过 200MB' }); + return; + } + + const video = document.createElement('video'); + video.preload = 'metadata'; + + video.onloadedmetadata = () => { + const duration = video.duration; + const height = video.videoHeight; + URL.revokeObjectURL(video.src); + + if (duration < 5) { + resolve({ + valid: false, + error: `视频时长 ${duration.toFixed(1)} 秒,要求至少 5 秒(建议 5-8 秒)`, + }); + return; + } + if (duration > 8) { + resolve({ + valid: false, + error: `视频时长 ${duration.toFixed(1)} 秒,要求不超过 8 秒(建议 5-8 秒)`, + }); + return; + } + if (height && (height < 720 || height > 2160)) { + resolve({ valid: false, error: `视频高度为 ${height}px,要求高度在 720px~2160px 之间` }); + return; + } + resolve({ valid: true }); + }; + + video.onerror = () => { + URL.revokeObjectURL(video.src); + resolve({ valid: false, error: '无法读取视频文件,请检查文件是否损坏' }); + }; + + setTimeout(() => { + URL.revokeObjectURL(video.src); + resolve({ valid: false, error: '读取视频超时,请重试' }); + }, 8000); + + video.src = URL.createObjectURL(file); + }); + }; + + const handleFileSelect = async (e: ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) { + return; + } + + const validation = await validateVideoFile(file); + if (!validation.valid) { + toast.error(validation.error || '视频验证失败'); + e.target.value = ''; + return; + } + + setSelectedFile(file); + }; + + + + // 将任务结果保存到本地形象库 + const saveAvatarResult = (result: unknown) => { + // ⚠️ 注意:client.ts 的 parseResponse 会对嵌套对象做 snakeToCamel 转换 + const r = result as + | { + avatarId?: string; + name?: string; + videoUrl?: string; + voiceId?: string; + elementId?: number; + trialUrl?: string; + } + | undefined; + if (r?.avatarId) { + const avatar: AvatarItem = { + id: r.avatarId, + name: r.name || avatarNameRef.current.trim() || '未命名形象', + voiceId: r.voiceId || '', + elementId: r.elementId || 0, + videoUrl: r.videoUrl || '', + trialUrl: r.trialUrl, + recordTime: new Date().toISOString(), + }; + addClonedAvatarToLocal(avatar).catch((err) => { + console.error('[AvatarUpload] Failed to save avatar:', err); + }); + } else { + // 缺少 avatarId,不保存到本地 + } + }; + + // 统一处理任务完成 + const handleTaskComplete = (result: unknown) => { + if (completedRef.current || !isActiveRef.current) { + return; + } + completedRef.current = true; + saveAvatarResult(result); + setCurrentStep('completed'); + setStatusText('创建成功!'); + // 不自动关闭,等用户点击"确定" + }; + + // 统一处理任务失败 + const handleTaskError = (error: string) => { + if (completedRef.current || !isActiveRef.current) { + return; + } + completedRef.current = true; + setCurrentStep('error'); + setStatusText(error || '创建失败'); + }; + + // 处理任务状态更新 + const handleTaskProgress = (taskStatus: string, _progress: number, message: string) => { + if (!isActiveRef.current || completedRef.current) { + return; + } + + // 更新状态文本 + if (message) { + setStatusText(message); + } + + switch (taskStatus) { + case 'failed': + handleTaskError(message || '创建失败'); + break; + case 'completed': + // 完成状态由 onComplete 回调统一处理 + break; + case 'pending': + case 'running': + // 保持当前状态,继续处理 + break; + default: + // 其他状态,保持显示 + break; + } + }; + + const handleUpload = async () => { + if (!avatarName.trim()) { + return; + } + if (!selectedFile) { + return; + } + + isActiveRef.current = true; + completedRef.current = false; + setCurrentStep('uploading'); + setStatusText('正在计算文件指纹...'); + + try { + // 计算文件哈希用于重复检测 + const fileHash = await calculateFileSHA256(selectedFile); + // fileHash 用于重复检测 + + setStatusText('正在上传视频...'); + + const uploadResult = await avatarApi.uploadAvatarVideo( + selectedFile, + avatarName.trim(), + fileHash + ); + + setCurrentStep('cloning'); + setStatusText('正在提交克隆任务...'); + + // 使用统一任务 API 提交形象克隆任务 + await submit( + 'avatar_clone', + { + name: avatarName.trim(), + videoUrl: uploadResult.url, + }, + { + showProgress: false, // 我们自己管理进度 UI + callbacks: { + onProgress: handleTaskProgress, + onComplete: (result) => { + handleTaskComplete(result); + }, + onError: (error) => { + handleTaskError(error); + }, + }, + } + ); + + setStatusText('等待任务调度...'); + } catch (error: unknown) { + const err = error as { name?: string; message?: string }; + if (err.name === 'AbortError') { + setStatusText('已取消'); + } else { + setCurrentStep('error'); + setStatusText(err.message || '创建失败'); + } + } + }; + + return ( + <> + {/* 非处理状态:普通 Modal 上传表单 */} + {currentStep === 'idle' && ( + +
+
+
+ + setAvatarName(e.target.value)} + disabled={currentStep !== 'idle'} + /> +
+ +
+ +
fileInputRef.current?.click()} + > + + {selectedFile ? ( +
+ + + + + + + + + + + {selectedFile.name} + + {(selectedFile.size / 1024 / 1024).toFixed(2)} MB + +
+ ) : ( +
+ + + + + + 点击选择视频文件 + + 支持 MP4 / MOV 格式,大小 ≤ 200MB +
+ 建议时长 5-8 秒,高度 720px ~ 2160px +
+ 需含清晰人声和人脸正面特写 +
+
+ )} +
+
+
+ +
+ + +
+
+
+ )} + + {/* 处理状态:全屏 ProgressModal 样式 Overlay */} + {currentStep !== 'idle' && open && ( +
+
+ {/* 头部 */} +
+
+ + + + +
+

形象克隆

+
+ + {/* 主体内容 */} +
+ {currentStep === 'completed' ? ( +
+
+ + + +
+

{statusText || '创建成功!'}

+
+ ) : currentStep === 'error' ? ( +
+
+ + + + + +
+

{statusText || '发生错误'}

+
+ ) : ( + <> +
+
+
+
+ {statusText} +
+ + )} +
+ + {/* 底部操作区 */} +
+ {currentStep === 'completed' && ( + + )} + {currentStep === 'error' && ( + + )} + {currentStep !== 'completed' && currentStep !== 'error' && ( + + 处理中,请勿退出应用 + + + )} +
+
+
+ )} + + ); +} diff --git a/tauri-app/src/components/Modal/ConfirmModal.css b/tauri-app/src/components/Modal/ConfirmModal.css new file mode 100644 index 0000000..2b22535 --- /dev/null +++ b/tauri-app/src/components/Modal/ConfirmModal.css @@ -0,0 +1,159 @@ +/* 确认弹窗样式 - 统一设计规范 */ + +.confirm-modal-overlay { + position: fixed; + inset: 0; + z-index: 1100; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.35); + backdrop-filter: blur(2px); + animation: confirmFadeIn 0.2s ease; +} + +.confirm-modal-container { + position: relative; + background: var(--bg-card); + border-radius: 16px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2); + width: 90%; + max-width: 360px; + padding: 24px; + display: flex; + flex-direction: column; + align-items: center; + animation: confirmSlideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1); +} + +/* 关闭按钮 */ +.confirm-modal-close { + position: absolute; + top: 12px; + right: 12px; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + background: transparent; + color: var(--text-tertiary); + cursor: pointer; + border: none; + transition: all 0.15s ease; +} + +.confirm-modal-close:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +/* 图标 */ +.confirm-modal-icon { + width: 48px; + height: 48px; + border-radius: 50%; + background: var(--bg-input); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 16px; +} + +/* 标题 */ +.confirm-modal-title { + font-size: 15px; + font-weight: 500; + color: var(--text-primary); + text-align: center; + line-height: 1.5; + margin-bottom: 20px; + word-break: break-word; +} + +.confirm-modal-title strong { + font-weight: 600; + color: var(--text-primary); +} + +/* 描述 */ +.confirm-modal-description { + font-size: 13px; + color: var(--text-secondary); + text-align: center; + line-height: 1.5; + margin-top: -12px; + white-space: pre-line; + margin-bottom: 20px; +} + +/* 按钮组 */ +.confirm-modal-actions { + display: flex; + gap: 10px; + width: 100%; +} + +.confirm-modal-btn { + flex: 1; + height: 40px; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + border: none; + transition: all 0.15s ease; + display: flex; + align-items: center; + justify-content: center; +} + +/* 取消按钮 */ +.confirm-modal-btn.cancel { + background: var(--bg-input); + color: var(--text-secondary); + border: 1px solid var(--border-light); +} + +.confirm-modal-btn.cancel:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +/* 确认按钮 - 主色 */ +.confirm-modal-btn.primary { + background: var(--primary); + color: white; +} + +.confirm-modal-btn.primary:hover { + background: var(--primary-hover); +} + +/* 确认按钮 - 危险色(统一使用绿色主色调) */ +.confirm-modal-btn.danger { + background: var(--primary); + color: white; +} + +.confirm-modal-btn.danger:hover { + background: var(--primary-hover); +} + +/* 动画 */ +@keyframes confirmFadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes confirmSlideUp { + from { + opacity: 0; + transform: translateY(20px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} diff --git a/tauri-app/src/components/Modal/ConfirmModal.tsx b/tauri-app/src/components/Modal/ConfirmModal.tsx new file mode 100644 index 0000000..2255d29 --- /dev/null +++ b/tauri-app/src/components/Modal/ConfirmModal.tsx @@ -0,0 +1,141 @@ +/** + * 确认弹窗组件 - 统一样式 + * ======================== + * + * 使用场景:删除确认、退出确认、危险操作确认 + * + * 示例: + * } + * title={<>确认删除形象 「{name}」 吗?} + * description="此操作不可撤销,本地文件将同时被清除" + * confirmText="确认删除" + * onConfirm={handleConfirm} + * onCancel={handleCancel} + * /> + */ + +import './ConfirmModal.css'; + +interface ConfirmModalProps { + open: boolean; + onClose?: () => void; + onConfirm: () => void; + onCancel: () => void; + type?: 'warning' | 'danger' | 'info'; + icon?: React.ReactNode; + title: React.ReactNode; + description?: string; + confirmText?: string; + cancelText?: string; + confirmButtonType?: 'primary' | 'danger'; +} + +// 默认图标 - 警告 +const WarningIcon = () => ( + + + + + +); + +// 默认图标 - 信息 +const InfoIcon = () => ( + + + + + +); + +export default function ConfirmModal({ + open, + onClose, + onConfirm, + onCancel, + type = 'warning', + icon, + title, + description, + confirmText = '确认', + cancelText = '取消', + confirmButtonType = 'primary', +}: ConfirmModalProps) { + if (!open) return null; + + // 根据类型选择默认图标 + const getDefaultIcon = () => { + switch (type) { + case 'danger': + case 'warning': + return ; + case 'info': + default: + return ; + } + }; + + // 图标颜色(统一使用灰色调) + const getIconColor = () => { + switch (type) { + case 'danger': + case 'warning': + case 'info': + default: + return 'var(--text-tertiary)'; // 灰色 + } + }; + + const handleClose = () => { + onCancel(); + onClose?.(); + }; + + const handleConfirm = () => { + onConfirm(); + onClose?.(); + }; + + return ( +
+
e.stopPropagation()}> + {/* 关闭按钮 */} + + + {/* 图标 */} +
+ {icon || getDefaultIcon()} +
+ + {/* 标题 */} +
{title}
+ + {/* 描述 */} + {description && ( +
{description}
+ )} + + {/* 按钮组 */} +
+ + +
+
+
+ ); +} diff --git a/tauri-app/src/components/Modal/Modal.css b/tauri-app/src/components/Modal/Modal.css new file mode 100644 index 0000000..14fa040 --- /dev/null +++ b/tauri-app/src/components/Modal/Modal.css @@ -0,0 +1,59 @@ +.modal-overlay { + position: fixed; + inset: 0; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-overlay); + animation: fadeIn 0.15s ease; +} + +.modal-container { + background: var(--bg-card); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-lg); + width: 90%; + max-height: 80vh; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-md) var(--spacing-lg); + border-bottom: 1px solid var(--border-light); +} + +.modal-title { + font-size: var(--font-lg); + font-weight: 600; +} + +.modal-close { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-md); + background: transparent; + color: var(--text-tertiary); + cursor: pointer; + border: none; + transition: all var(--transition-fast); +} + +.modal-close:hover { + background: var(--bg-input); + color: var(--text-primary); +} + +.modal-body { + padding: var(--spacing-md); + overflow-y: auto; + flex: 1; +} diff --git a/tauri-app/src/components/Modal/Modal.tsx b/tauri-app/src/components/Modal/Modal.tsx new file mode 100644 index 0000000..313d058 --- /dev/null +++ b/tauri-app/src/components/Modal/Modal.tsx @@ -0,0 +1,85 @@ +import { useEffect, useRef } from 'react'; +import './Modal.css'; + +interface ModalProps { + open: boolean; + onClose: () => void; + title?: string; + children: React.ReactNode; + width?: string; + centerTitle?: boolean; +} + +export default function Modal({ + open, + onClose, + title, + children, + width = '560px', + centerTitle = false, +}: ModalProps) { + const overlayRef = useRef(null); + + useEffect(() => { + if (open) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = ''; + } + return () => { + document.body.style.overflow = ''; + }; + }, [open]); + + if (!open) { + return null; + } + + return ( +
{ + if (e.target === overlayRef.current) { + onClose(); + } + }} + > +
+ {title && ( +
+

+ {title} +

+ +
+ )} +
{children}
+
+
+ ); +} diff --git a/tauri-app/src/components/ProgressModal/ProgressModal.css b/tauri-app/src/components/ProgressModal/ProgressModal.css new file mode 100644 index 0000000..7444bd1 --- /dev/null +++ b/tauri-app/src/components/ProgressModal/ProgressModal.css @@ -0,0 +1,279 @@ +/** + * 全局进度弹窗样式 + */ + +.progress-modal-overlay { + position: fixed; + inset: 0; + z-index: 1300; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(4px); + animation: progressModalFadeIn 0.2s ease; +} + +/* 运行中状态 - 禁止关闭的提示 */ +.progress-modal-overlay.running { + cursor: not-allowed; +} + +.progress-modal-overlay.running .progress-modal-container { + cursor: default; +} + +.progress-modal-container { + background: rgba(255, 255, 255, 0.85); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-radius: var(--radius-xl, 16px); + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.1), + 0 2px 8px rgba(0, 0, 0, 0.05), + inset 0 1px 0 rgba(255, 255, 255, 0.6); + border: 1px solid rgba(255, 255, 255, 0.3); + width: 90%; + max-width: 360px; + padding: 28px 24px; + display: flex; + flex-direction: column; + gap: 20px; + animation: progressModalSlideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1); +} + +/* 头部标题 */ +.progress-modal-header { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; +} + +.progress-modal-icon-wrapper { + width: 56px; + height: 56px; + border-radius: 14px; + background: linear-gradient(135deg, rgba(34, 197, 94, 0.15) 0%, rgba(34, 197, 94, 0.05) 100%); + display: flex; + align-items: center; + justify-content: center; +} + +.progress-modal-icon-animated { + color: var(--primary, #22c55e); + animation: iconPulse 2s ease-in-out infinite; +} + +@keyframes iconPulse { + 0%, 100% { + transform: scale(1); + opacity: 1; + } + 50% { + transform: scale(1.05); + opacity: 0.8; + } +} + +.progress-modal-title { + font-size: 18px; + font-weight: 600; + color: var(--text-primary, #1a1a1a); + margin: 0; +} + +/* 进度条区域 */ +.progress-modal-body { + display: flex; + flex-direction: column; + gap: 12px; +} + +.progress-modal-bar-container { + width: 100%; + height: 12px; + background: #e8e8e8; + border-radius: 6px; + overflow: hidden; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.progress-modal-bar-fill { + height: 100%; + background: linear-gradient(180deg, #4ade80 0%, #22c55e 50%, #16a34a 100%); + border-radius: 6px; + position: relative; + box-shadow: 0 1px 2px rgba(34, 197, 94, 0.3); +} + +/* 不确定进度条(running 状态) */ +.progress-modal-bar-indeterminate { + height: 100%; + width: 40%; + background: linear-gradient(90deg, #4ade80 0%, #22c55e 50%, #16a34a 100%); + border-radius: 6px; + animation: indeterminateSlide 1.5s infinite ease-in-out; + box-shadow: 0 1px 2px rgba(34, 197, 94, 0.3); +} + +@keyframes indeterminateSlide { + 0% { + transform: translateX(-100%); + } + 50% { + transform: translateX(150%); + } + 100% { + transform: translateX(-100%); + } +} + +/* 成功态 */ +.progress-modal-success-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + padding: 8px 0; +} + +.progress-modal-success-icon { + color: var(--primary, #22c55e); +} + +.progress-modal-success-message { + font-size: 14px; + color: var(--text-secondary, #666666); + text-align: center; + margin: 0; +} + +/* 状态信息 */ +.progress-modal-info { + display: flex; + justify-content: center; + align-items: center; + font-size: 14px; +} + +.progress-modal-status { + color: var(--text-secondary, #666666); +} + +/* 底部操作区 */ +.progress-modal-footer { + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + min-height: 24px; +} + +.progress-modal-hint { + font-size: 13px; + color: var(--text-tertiary, #999999); +} + +.progress-modal-loading-dots::after { + content: '...'; + display: inline-block; + width: 1.2em; + text-align: left; + animation: progressModalLoadingDots 1.2s steps(3, end) infinite; +} + +@keyframes progressModalLoadingDots { + 0% { content: ''; } + 33% { content: '.'; } + 66% { content: '..'; } + 100% { content: '...'; } +} + +/* 成功态步骤对勾 */ +/* 错误内容 */ +.progress-modal-error-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + padding: 8px 0; +} + +.progress-modal-error-icon { + color: var(--error, #ef4444); +} + +.progress-modal-error-message { + font-size: 14px; + color: var(--text-secondary, #666666); + text-align: center; + margin: 0; +} + +.progress-modal-btn { + padding: 10px 32px; + background: var(--primary, #22c55e); + color: white; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.progress-modal-btn:hover { + opacity: 0.9; + transform: translateY(-1px); +} + +.progress-modal-btn:active { + transform: translateY(0); +} + +/* 错误关闭按钮 */ +.progress-modal-btn-error { + background: var(--error, #ef4444); +} + +/* 动画 */ +@keyframes progressModalFadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes progressModalSlideUp { + from { + opacity: 0; + transform: translateY(20px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +/* 暗黑模式适配 */ +[data-theme='dark'] .progress-modal-container { + background: rgba(40, 40, 40, 0.85); + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.3), + 0 2px 8px rgba(0, 0, 0, 0.2), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +[data-theme='dark'] .progress-modal-title { + color: var(--text-primary, #ffffff); +} + +[data-theme='dark'] .progress-modal-status { + color: var(--text-secondary, #a0a0a0); +} + + diff --git a/tauri-app/src/components/ProgressModal/ProgressModal.tsx b/tauri-app/src/components/ProgressModal/ProgressModal.tsx new file mode 100644 index 0000000..55945e7 --- /dev/null +++ b/tauri-app/src/components/ProgressModal/ProgressModal.tsx @@ -0,0 +1,159 @@ +/** + * 全局进度弹窗组件 + * ================= + * + * running 状态使用不确定进度条(CSS 条纹动画)+ 阶段文案 + * success / error 状态显示结果图标 + */ + +import { useCallback, useEffect, useState } from 'react'; +import { useProgressStore } from '../../store/progressStore'; +import './ProgressModal.css'; + +export default function ProgressModal() { + const { visible, title, status, phase, errorMessage, hide } = useProgressStore(); + + const isError = phase === 'error'; + const isSuccess = phase === 'success'; + const isRunning = phase === 'running'; + + // 成功按钮延迟出现,避免完成瞬间底部突变 + const [showButton, setShowButton] = useState(false); + useEffect(() => { + if (isSuccess) { + const timer = setTimeout(() => setShowButton(true), 100); + return () => clearTimeout(timer); + } else { + setShowButton(false); + } + }, [isSuccess]); + + const handleClose = useCallback(() => { + if (phase === 'success' || phase === 'error') { + hide(); + } + }, [phase, hide]); + + const handleOverlayClick = useCallback(() => { + if (phase === 'running') return; + handleClose(); + }, [phase, handleClose]); + + if (!visible) { + return null; + } + + return ( +
+
e.stopPropagation()}> + {/* 头部标题 */} +
+ {(title.includes('脚本') || title.includes('文案')) && ( +
+ + + + + + + +
+ )} + {(title.includes('视频') || title.includes('合成')) && ( +
+ + + + + +
+ )} + {(title.includes('字幕') || title.includes('压制')) && ( +
+ + + + + + +
+ )} + {(title.includes('图片') || title.includes('封面')) && ( +
+ + + + + +
+ )} + {(title.includes('形象') || title.includes('克隆')) && ( +
+ + + + +
+ )} +

{title}

+
+ + {/* 主体内容 */} +
+ {isError ? ( +
+
+ + + + + +
+

{errorMessage || '发生错误'}

+
+ ) : isSuccess ? ( +
+
+ + + +
+

{status || '生成完成'}

+
+ ) : ( + <> + {/* 不确定进度条 */} +
+
+
+
+ {status} +
+ + )} +
+ + {/* 底部操作区 */} +
+ {isSuccess && showButton ? ( + + ) : isError ? ( + + ) : ( + + 处理中,请勿退出应用 + + + )} +
+
+
+ ); +} diff --git a/tauri-app/src/components/ShotStats/ShotStats.css b/tauri-app/src/components/ShotStats/ShotStats.css new file mode 100644 index 0000000..9e72e9a --- /dev/null +++ b/tauri-app/src/components/ShotStats/ShotStats.css @@ -0,0 +1,112 @@ +/** + * ShotStats 组件样式 + * ================== + * + * 统一的分镜统计组件样式 + * 使用网格布局展示统计数据 + */ + +.shot-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: var(--spacing-md); +} + +.shot-stats-item { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--spacing-xs); + padding: var(--spacing-md); + background: var(--bg-card); + border-radius: var(--radius-lg); + border: 1px solid var(--border-light); + box-shadow: var(--shadow-sm); + transition: all var(--transition-fast); +} + +.shot-stats-item:hover { + border-color: var(--primary-light); + box-shadow: var(--shadow-md); + transform: translateY(-1px); +} + +.shot-stats-icon { + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + color: var(--primary); + opacity: 0.8; +} + +.shot-stats-value { + font-size: var(--font-xl); + font-weight: 700; + color: var(--primary); + line-height: 1.2; + text-align: center; +} + +.shot-stats-label { + font-size: var(--font-xs); + color: var(--text-tertiary); + font-weight: 500; + text-align: center; +} + +/* 紧凑型变体 */ +.shot-stats-compact { + display: flex; + gap: var(--spacing-xl); + padding: var(--spacing-md) 0; +} + +.shot-stats-compact .shot-stats-item { + flex-direction: row; + gap: var(--spacing-xs); + padding: 0; + background: transparent; + border: none; + box-shadow: none; +} + +.shot-stats-compact .shot-stats-item:hover { + transform: none; +} + +.shot-stats-compact .shot-stats-icon { + width: 32px; + height: 32px; + border-radius: var(--radius-md); + background: var(--bg-input); + color: var(--text-secondary); + opacity: 1; +} + +.shot-stats-compact .shot-stats-value { + font-size: var(--font-lg); + color: var(--text-primary); +} + +.shot-stats-compact .shot-stats-label { + font-size: var(--font-xs); +} + +/* 响应式 */ +@media (width <= 640px) { + .shot-stats { + grid-template-columns: repeat(2, 1fr); + } + + .shot-stats-compact { + flex-wrap: wrap; + } + + .shot-stats-compact .shot-stats-item { + flex: 1; + min-width: 100px; + } +} diff --git a/tauri-app/src/components/ShotStats/ShotStats.tsx b/tauri-app/src/components/ShotStats/ShotStats.tsx new file mode 100644 index 0000000..d09bab7 --- /dev/null +++ b/tauri-app/src/components/ShotStats/ShotStats.tsx @@ -0,0 +1,91 @@ +/** + * ShotStats 组件 + * ============== + * + * 显示分镜统计信息(字数、时长、分镜数、空镜数) + * 用于 ScriptCreation 等页面 + */ + +import React from 'react'; +import './ShotStats.css'; + +export interface ShotStatsData { + totalWords: number; + totalDuration: number; + segmentCount: number; + emptyShotCount: number; +} + +interface ShotStatsProps { + stats: ShotStatsData; + className?: string; +} + +/** + * 分镜统计组件 + */ +export const ShotStats: React.FC = ({ stats, className = '' }) => { + const { totalWords, totalDuration, segmentCount /* , emptyShotCount */ } = stats; + + return ( +
+ } value={totalWords} label="总字数" /> + } value={`${totalDuration}s`} label="预计时长" /> + } value={segmentCount} label="分镜数" /> + {/* 空镜功能暂时禁用 */} + {/* } value={emptyShotCount} label="空镜数" /> */} +
+ ); +}; + +// 子组件:单个统计项 +interface StatItemProps { + icon: React.ReactNode; + value: string | number; + label: string; +} + +const StatItem: React.FC = ({ icon, value, label }) => ( +
+
{icon}
+ {value} + {label} +
+); + +// 图标组件 +const WordIcon = () => ( + + + + + + + +); + +const DurationIcon = () => ( + + + + +); + +const SegmentIcon = () => ( + + + + + +); + +/* 空镜功能暂时禁用 */ +// const EmptyShotIcon = () => ( +// +// +// +// +// +// ); + +export default ShotStats; diff --git a/tauri-app/src/components/ShotStats/index.ts b/tauri-app/src/components/ShotStats/index.ts new file mode 100644 index 0000000..23ed7d9 --- /dev/null +++ b/tauri-app/src/components/ShotStats/index.ts @@ -0,0 +1,2 @@ +export { ShotStats, type ShotStatsData } from './ShotStats'; +export { default } from './ShotStats'; diff --git a/tauri-app/src/components/Slider/Slider.css b/tauri-app/src/components/Slider/Slider.css new file mode 100644 index 0000000..9845e91 --- /dev/null +++ b/tauri-app/src/components/Slider/Slider.css @@ -0,0 +1,95 @@ +.slider-group { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.slider-header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.slider-label { + font-size: var(--font-sm); + color: var(--text-secondary); + font-weight: 500; +} + +.slider-value { + font-size: var(--font-sm); + color: var(--primary); + font-weight: 600; + min-width: 48px; + text-align: right; +} + +.slider-track-wrapper { + position: relative; +} + +.slider-input { + appearance: none; + appearance: none; + width: 100%; + height: 6px; + background: transparent; + border-radius: var(--radius-sm); + outline: none; + cursor: pointer; + border: none; + padding: 0; +} + +/* WebKit 轨道 - 已选择部分 */ +.slider-input::-webkit-slider-runnable-track { + width: 100%; + height: 6px; + background: linear-gradient( + to right, + var(--primary) 0%, + var(--primary) var(--slider-percent, 50%), + var(--bg-input) var(--slider-percent, 50%), + var(--bg-input) 100% + ); + border-radius: var(--radius-sm); +} + +/* Firefox 轨道 */ +.slider-input::-moz-range-track { + width: 100%; + height: 6px; + background: var(--bg-input); + border-radius: var(--radius-sm); +} + +/* Firefox 已选择部分 */ +.slider-input::-moz-range-progress { + background: var(--primary); + height: 6px; + border-radius: var(--radius-sm); +} + +.slider-input::-webkit-slider-thumb { + appearance: none; + appearance: none; + width: 18px; + height: 18px; + background: var(--bg-card); + border: 2px solid var(--primary); + border-radius: var(--radius-full); + cursor: pointer; + box-shadow: 0 1px 4px rgb(0 0 0 / 15%); + transition: + transform 0.15s ease, + box-shadow 0.15s ease; +} + +.slider-input::-webkit-slider-thumb:hover { + transform: scale(1.15); + box-shadow: 0 2px 8px rgb(54 178 106 / 30%); +} + +.slider-input:focus::-webkit-slider-thumb { + box-shadow: 0 0 0 4px var(--primary-light); +} diff --git a/tauri-app/src/components/Toast/Toast.css b/tauri-app/src/components/Toast/Toast.css new file mode 100644 index 0000000..2f6f9cc --- /dev/null +++ b/tauri-app/src/components/Toast/Toast.css @@ -0,0 +1,95 @@ +.toast-container { + position: fixed; + top: 40px; + left: 50%; + transform: translateX(-50%); + z-index: 9999; + display: flex; + flex-direction: column; + gap: var(--spacing-lg); + pointer-events: none; +} + +.toast-item { + padding: var(--spacing-sm) var(--spacing-3xl); + border-radius: var(--radius-xl); + font-size: var(--font-base); + font-weight: 500; + box-shadow: 0 8px 32px rgb(0 0 0 / 8%); + display: flex; + align-items: center; + position: relative; + pointer-events: auto; + min-width: 260px; + justify-content: center; + background: var(--bg-card); + color: var(--text-primary); + border: 1px solid var(--border-light); + backdrop-filter: blur(20px); + backdrop-filter: blur(20px); + animation: slideDownToast 0.4s cubic-bezier(0.16, 1, 0.3, 1); + transition: all var(--transition-normal); +} + +@keyframes slideDownToast { + from { + opacity: 0; + transform: translateY(-20px) scale(0.95); + } + + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.toast-icon { + position: absolute; + left: 14px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + padding: var(--spacing-xs); +} + +.toast-message { + text-align: center; + flex: 1; +} + +.toast-info { + border: 1px solid rgba(var(--primary-rgb), 0.3); +} + +.toast-info .toast-icon { + background: rgba(var(--primary-rgb), 0.15); + color: var(--primary); +} + +.toast-success { + border: 1px solid rgba(var(--success-rgb), 0.3); +} + +.toast-success .toast-icon { + background: rgba(var(--success-rgb), 0.15); + color: var(--success); +} + +.toast-error { + border: 1px solid rgba(var(--error-rgb), 0.3); +} + +.toast-error .toast-icon { + background: rgba(var(--error-rgb), 0.15); + color: var(--error); +} + +.toast-warning { + border: 1px solid rgba(var(--warning-rgb), 0.3); +} + +.toast-warning .toast-icon { + background: rgba(var(--warning-rgb), 0.15); + color: var(--warning); +} diff --git a/tauri-app/src/components/Toast/ToastContainer.tsx b/tauri-app/src/components/Toast/ToastContainer.tsx new file mode 100644 index 0000000..ec520e1 --- /dev/null +++ b/tauri-app/src/components/Toast/ToastContainer.tsx @@ -0,0 +1,87 @@ +import { useUIStore, type ToastMessage } from '../../store/uiStore'; +import './Toast.css'; + +export default function ToastContainer() { + const { toasts } = useUIStore(); + + return ( +
+ {toasts.map((t: ToastMessage) => ( +
+ {t.type === 'success' && ( +
+ + + + +
+ )} + {t.type === 'error' && ( +
+ + + + + +
+ )} + {t.type === 'info' && ( +
+ + + + + +
+ )} + {t.type === 'warning' && ( +
+ + + + + +
+ )} + {t.message} +
+ ))} +
+ ); +} diff --git a/tauri-app/src/hooks/useAssJsRenderer.ts b/tauri-app/src/hooks/useAssJsRenderer.ts new file mode 100644 index 0000000..9425fc7 --- /dev/null +++ b/tauri-app/src/hooks/useAssJsRenderer.ts @@ -0,0 +1,107 @@ +/** + * ASS.js 字幕渲染 Hook(video 模式) + * =================================== + * + * 基于 assjs 的纯 JavaScript DOM 字幕渲染,无 WASM/Worker 依赖。 + * 字体由浏览器 CSS 处理,自动支持系统字体回退(PingFang SC / Microsoft YaHei)。 + * + * 问题修复: + * - 第一次加载视频,metadata 未就绪就初始化 → 时间计算错误 + * - 修复:等待 loadedmetadata 后再初始化 + */ + +import { useEffect, useRef, useState } from 'react'; +import ASS from 'assjs'; + +interface UseAssJsOptions { + videoRef: React.RefObject; + containerRef: React.RefObject; + assContent: string | null; + enabled: boolean; +} + +export function useAssJsRenderer(options: UseAssJsOptions) { + const instanceRef = useRef | null>(null); + const styleRef = useRef(null); + + // 关键修复:每次 assContent / enabled 变化时,重置就绪状态,重新等待 metadata + const [videoReady, setVideoReady] = useState(false); + + // 每次依赖变化,重置就绪状态 + useEffect(() => { + setVideoReady(false); + + const video = options.videoRef.current; + if (!options.enabled || !video || !options.assContent) { + return; + } + + // 已经有正确 metadata + if (video.readyState >= 1) { + setVideoReady(true); + return; + } + + // 等待 metadata 加载完成 + const onLoadedMetadata = () => { + setVideoReady(true); + }; + video.addEventListener('loadedmetadata', onLoadedMetadata); + return () => { + video.removeEventListener('loadedmetadata', onLoadedMetadata); + }; + }, [options.enabled, options.assContent, options.videoRef]); + + useEffect(() => { + const video = options.videoRef.current; + const container = options.containerRef.current; + + // 总是先清理旧实例 + if (instanceRef.current) { + instanceRef.current.destroy(); + instanceRef.current = null; + } + if (styleRef.current) { + styleRef.current.remove(); + styleRef.current = null; + } + + // 条件不满足 → 不创建新实例 + if (!options.enabled || !video || !container || !options.assContent || !videoReady) { + return; + } + + console.log('[ASS.js] creating instance (video ready)'); + try { + // 注入 CSS:隐藏 assjs 的 :before 阴影伪元素(shadow 已设为 0,不需要这层) + // 注意选择器必须有空格(后代选择器),assjs 的 DOM 结构是 .ASS-dialogue > [data-border-style="1"] + const styleEl = document.createElement('style'); + styleEl.textContent = '.ASS-dialogue [data-border-style="1"]::before{display:none !important}'; + container.appendChild(styleEl); + styleRef.current = styleEl; + + const instance = new ASS(options.assContent, video, { + container, + resampling: 'video_height', + }); + instanceRef.current = instance; + console.log('[ASS.js] instance created'); + } catch (err) { + console.error('[ASS.js] init failed:', err); + } + + return () => { + if (instanceRef.current) { + console.log('[ASS.js] destroying instance'); + instanceRef.current.destroy(); + instanceRef.current = null; + } + if (styleRef.current) { + styleRef.current.remove(); + styleRef.current = null; + } + }; + // assContent 变化时需要重建实例 + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [options.enabled, options.assContent, videoReady]); +} diff --git a/tauri-app/src/hooks/useAvatarCache.ts b/tauri-app/src/hooks/useAvatarCache.ts new file mode 100644 index 0000000..3b4dda8 --- /dev/null +++ b/tauri-app/src/hooks/useAvatarCache.ts @@ -0,0 +1,315 @@ +/** + * 形象本地缓存 Hook + * ================= + * + * 通过 Tauri IPC 调用 Rust 端缓存服务,将远程视频和封面缓存到本地文件系统 + * 前端使用 fs.readFile + Blob URL 加载本地文件,绕过 asset protocol 404 问题 + */ + +import { invoke } from '@tauri-apps/api/core'; +import { readFile } from '@tauri-apps/plugin-fs'; +import { useState, useEffect, useRef } from 'react'; + +/** 缓存查询结果 */ +export interface CacheQueryResult { + cached: boolean; + local_video_path: string | null; + local_poster_path: string | null; +} + +/** 缓存保存结果 */ +export interface CacheSaveResult { + success: boolean; + localPath: string | null; + message: string | null; +} + +/** 删除结果 */ +export interface CacheDeleteResult { + success: boolean; + message: string | null; +} + +/** + * 查询头像缓存状态 + */ +export async function queryAvatarCache(avatarId: string): Promise { + const result = await invoke('query_avatar_cache', { avatarId }); + return result; +} + +/** + * 下载远程视频并缓存到本地 + */ +export async function cacheAvatarVideo( + avatarId: string, + remoteUrl: string +): Promise { + const result = await invoke('cache_avatar_video', { + avatarId, + remoteUrl, + }); + return result; +} + +/** + * 保存封面(base64)到缓存 + */ +export async function saveAvatarPoster( + avatarId: string, + base64Data: string +): Promise { + const result = await invoke('save_avatar_poster', { + avatarId, + base64Data, + }); + return result; +} + +/** + * 删除头像缓存 + */ +export async function deleteAvatarCache(avatarId: string): Promise { + const result = await invoke('delete_avatar_cache', { avatarId }); + return result; +} + +/** 缓存统计信息 */ +export interface CacheStats { + count: number; + totalSizeMb: number; + maxSizeMb: number; + usagePercent: number; + thresholdPercent: number; +} + +/** + * 获取缓存统计信息 + */ +export async function getCacheStats(): Promise { + const result = await invoke<{ + code: number; + data: { + count: number; + total_size_mb: number; + max_size_mb: number; + usage_percent: number; + threshold_percent: number; + }; + }>('get_cache_stats'); + return { + count: result.data.count, + totalSizeMb: result.data.total_size_mb, + maxSizeMb: result.data.max_size_mb, + usagePercent: result.data.usage_percent, + thresholdPercent: result.data.threshold_percent, + }; +} + +/** + * Hook: 获取带缓存的视频URL + * - 优先返回本地缓存的 Blob URL + * - 没有缓存则返回远程URL,并在后台下载缓存 + */ +export function useCachedVideoUrl( + avatarId: string, + remoteUrl: string | undefined +): { + videoUrl: string | undefined; + isCached: boolean; + isCaching: boolean; +} { + const [videoUrl, setVideoUrl] = useState(remoteUrl); + const [isCached, setIsCached] = useState(false); + const [isCaching, setIsCaching] = useState(false); + const blobUrlRef = useRef(null); + + useEffect(() => { + if (!avatarId || !remoteUrl) { + // 清理旧 Blob URL + if (blobUrlRef.current) { + URL.revokeObjectURL(blobUrlRef.current); + blobUrlRef.current = null; + } + setVideoUrl(remoteUrl); + setIsCached(false); + return; + } + + let canceled = false; + + async function checkCache() { + try { + const result = await queryAvatarCache(avatarId); + if (canceled) return; + + if (result.cached && result.local_video_path) { + // 有缓存,读取本地文件创建 Blob URL + const data = await readFile(result.local_video_path); + if (canceled) return; + + const blob = new Blob([data], { type: 'video/mp4' }); + const url = URL.createObjectURL(blob); + if (blobUrlRef.current) { + URL.revokeObjectURL(blobUrlRef.current); + } + blobUrlRef.current = url; + + setVideoUrl(url); + setIsCached(true); + setIsCaching(false); + } else if (remoteUrl) { + // 没有缓存,开始后台下载 + setIsCaching(true); + const cacheResult = await cacheAvatarVideo(avatarId, remoteUrl); + if (canceled) return; + + if (cacheResult.success && cacheResult.localPath) { + const data = await readFile(cacheResult.localPath); + if (canceled) return; + + const blob = new Blob([data], { type: 'video/mp4' }); + const url = URL.createObjectURL(blob); + if (blobUrlRef.current) { + URL.revokeObjectURL(blobUrlRef.current); + } + blobUrlRef.current = url; + + setVideoUrl(url); + setIsCached(true); + } + setIsCaching(false); + } + } catch (e) { + console.warn('[useAvatarCache] Cache check failed:', e); + if (!canceled) { + // 缓存失败,降级使用远程URL + setVideoUrl(remoteUrl); + setIsCaching(false); + } + } + } + + checkCache(); + + return () => { + canceled = true; + }; + }, [avatarId, remoteUrl]); + + // 组件卸载时释放 Blob URL + useEffect(() => { + return () => { + if (blobUrlRef.current) { + URL.revokeObjectURL(blobUrlRef.current); + blobUrlRef.current = null; + } + }; + }, []); + + return { + videoUrl, + isCached, + isCaching, + }; +} + +/** + * Hook: 获取带缓存的封面URL + */ +export function useCachedPosterUrl( + avatarId: string, + generatePoster: () => Promise +): { + posterUrl: string; + isLoaded: boolean; + loadPoster: () => Promise; +} { + const [posterUrl, setPosterUrl] = useState(''); + const [isLoaded, setIsLoaded] = useState(false); + const blobUrlRef = useRef(null); + + useEffect(() => { + let canceled = false; + + async function checkAndGenerate() { + try { + // 先检查是否已有缓存的海报 + const result = await queryAvatarCache(avatarId); + if (canceled) return; + + if (result.cached && result.local_poster_path) { + // 有缓存,读取本地文件创建 Blob URL + const data = await readFile(result.local_poster_path); + if (canceled) return; + + const blob = new Blob([data], { type: 'image/jpeg' }); + const url = URL.createObjectURL(blob); + if (blobUrlRef.current) { + URL.revokeObjectURL(blobUrlRef.current); + } + blobUrlRef.current = url; + + setPosterUrl(url); + setIsLoaded(true); + return; + } + + // 没有缓存,生成新海报 + const generated = await generatePoster(); + if (canceled) return; + + if (generated) { + setPosterUrl(generated); + setIsLoaded(true); + + // 保存到缓存(后台,不等待) + if (generated.startsWith('data:')) { + saveAvatarPoster(avatarId, generated).catch(e => { + console.warn('[useAvatarCache] Failed to save poster to cache:', e); + }); + } + } else { + // 生成失败,使用占位符 + setPosterUrl(''); + setIsLoaded(true); + } + } catch (e) { + console.warn('[useAvatarCache] Poster cache check failed:', e); + } + } + + checkAndGenerate(); + + return () => { + canceled = true; + }; + }, [avatarId, generatePoster]); + + // 组件卸载时释放 Blob URL + useEffect(() => { + return () => { + if (blobUrlRef.current) { + URL.revokeObjectURL(blobUrlRef.current); + blobUrlRef.current = null; + } + }; + }, []); + + return { + posterUrl, + isLoaded, + loadPoster: async () => { + const generated = await generatePoster(); + setPosterUrl(generated); + setIsLoaded(true); + if (generated.startsWith('data:')) { + try { + await saveAvatarPoster(avatarId, generated); + } catch (e) { + console.warn('[useAvatarCache] Failed to save poster to cache:', e); + } + } + }, + }; +} diff --git a/tauri-app/src/hooks/useAvatarLibrary.ts b/tauri-app/src/hooks/useAvatarLibrary.ts new file mode 100644 index 0000000..4c9ecca --- /dev/null +++ b/tauri-app/src/hooks/useAvatarLibrary.ts @@ -0,0 +1,123 @@ +/** + * Avatar Library Hook - SWR + * ========================= + * + * 提供自动缓存、重验证、错误重试的形象库数据获取 + * + * 设计策略:克隆形象完全本地存储 + * - 克隆形象仅保存在 Tauri 本地文件存储,不再同步云端 + * - 任务完成后自动从 taskStore 同步到本地形象库 + * - 预设形象仍来自后端 API + * + * 形象库组成: + * - 预设形象:来自后端 API + * - 克隆形象:来自 Tauri 本地文件存储 + */ + +import useSWR from 'swr'; +import { avatarApi, type AvatarItem } from '../api/modules/avatar'; +import { + loadClonedAvatarsFromLocal, + syncCompletedAvatarClonesFromTaskStore, +} from '../utils/avatarStorage'; + +const AVATAR_LIBRARY_KEY = 'avatar-library'; + +/** + * 合并预设形象和克隆形象 + * - 使用 Map 去重,保证 id 唯一 + */ +function mergeAvatarLibrary( + presetAvatars: AvatarItem[], + clonedAvatars: AvatarItem[] +): AvatarItem[] { + // 使用 Map 去重,以 id 为准 + const avatarMap = new Map(); + + // 先加预设 + presetAvatars.forEach(avatar => { + if (avatar.id) { + avatarMap.set(String(avatar.id), avatar); + } + }); + + // 再加克隆形象,覆盖冲突 + clonedAvatars.forEach(avatar => { + if (avatar.id) { + avatarMap.set(String(avatar.id), avatar); + } + }); + + return Array.from(avatarMap.values()); +} + +/** + * 获取形象库列表 + * - 优先使用本地文件存储 + * - 本地为空时从后端获取云端备份并保存到本地 + * - 窗口聚焦时自动重新验证(如果本地为空,重新拉取) + */ +export function useAvatarLibrary() { + const { data, error, isLoading, mutate } = useSWR( + AVATAR_LIBRARY_KEY, + async () => { + // 1. 先将已完成的任务结果同步到本地形象库 + await syncCompletedAvatarClonesFromTaskStore(); + + // 2. 加载本地克隆形象(Tauri 文件存储) + const localClonedAvatars = await loadClonedAvatarsFromLocal(); + + // 3. 获取预设形象(始终从后端获取) + const presetAvatars = await avatarApi.getPresetLibrary(); + + // 4. 合并数据(克隆形象完全本地存储,不再走云端备份) + return mergeAvatarLibrary(presetAvatars, localClonedAvatars); + }, + { + revalidateOnFocus: true, + revalidateOnReconnect: true, + dedupingInterval: 5000, + errorRetryCount: 3, + errorRetryInterval: 1000, + } + ); + + return { + avatarLibrary: data ?? [], + isLoading, + error, + mutate, + }; +} + +/** + * 获取指定形象信息 + */ +export function useAvatarInfo(avatarId: string | undefined) { + const { avatarLibrary, isLoading } = useAvatarLibrary(); + + return { + avatarInfo: avatarId ? avatarLibrary.find(a => a.id === avatarId) : undefined, + isLoading, + }; +} + +/** + * 按分类筛选形象 + * 克隆形象通过 elementId > 0 识别;预设形象暂无分类字段时通过是否有 elementId 辅助判断 + */ +export function useAvatarsByCategory(category: 'preset' | 'clone') { + const { avatarLibrary, isLoading, error, mutate } = useAvatarLibrary(); + + const filtered = + category === 'clone' + ? avatarLibrary.filter(a => a.elementId && a.elementId > 0) + : avatarLibrary.filter(a => !a.elementId); + + return { + avatars: filtered, + isLoading, + error, + mutate, + }; +} diff --git a/tauri-app/src/hooks/useLocalImage.ts b/tauri-app/src/hooks/useLocalImage.ts new file mode 100644 index 0000000..7d95bf0 --- /dev/null +++ b/tauri-app/src/hooks/useLocalImage.ts @@ -0,0 +1,96 @@ +/** + * 本地图片加载 Hook + * ================= + * + * 使用 Tauri fs API 读取本地图片文件,创建 Blob URL 供 img 标签使用 + * 绕过 asset:// protocol 的权限问题 + */ + +import { useState, useEffect, useRef } from 'react'; +import { readFile } from '@tauri-apps/plugin-fs'; +import { homeDir } from '@tauri-apps/api/path'; + +interface UseLocalImageResult { + imageUrl: string | undefined; + isLoading: boolean; + error: string | null; +} + +/** + * 加载本地图片文件,返回 Blob URL + * + * @param filePath 本地文件绝对路径 + * @returns Blob URL 或 undefined + */ +export function useLocalImage(filePath: string | undefined): UseLocalImageResult { + const [imageUrl, setImageUrl] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const prevUrlRef = useRef(undefined); + + useEffect(() => { + if (prevUrlRef.current && prevUrlRef.current.startsWith('blob:')) { + URL.revokeObjectURL(prevUrlRef.current); + } + + if (!filePath) { + setImageUrl(undefined); + setIsLoading(false); + setError(null); + return; + } + + if (filePath.startsWith('http')) { + setImageUrl(filePath); + setIsLoading(false); + prevUrlRef.current = filePath; + return; + } + + let canceled = false; + + async function resolveHostPath(dockerPath: string): Promise { + if (dockerPath.startsWith('/root/')) { + const home = await homeDir(); + return dockerPath.replace(/^\/root/, home); + } + return dockerPath; + } + + async function loadImage() { + setIsLoading(true); + setError(null); + + try { + const resolvedPath = await resolveHostPath(filePath!); + const data = await readFile(resolvedPath); + + if (canceled) return; + + const blob = new Blob([data], { type: 'image/jpeg' }); + const url = URL.createObjectURL(blob); + + if (!canceled) { + setImageUrl(url); + prevUrlRef.current = url; + } + } catch (err: any) { + if (!canceled) { + setError(err?.message || '加载图片失败'); + } + } finally { + if (!canceled) { + setIsLoading(false); + } + } + } + + loadImage(); + + return () => { + canceled = true; + }; + }, [filePath]); + + return { imageUrl, isLoading, error }; +} diff --git a/tauri-app/src/hooks/useLocalVideo.ts b/tauri-app/src/hooks/useLocalVideo.ts new file mode 100644 index 0000000..c7558f9 --- /dev/null +++ b/tauri-app/src/hooks/useLocalVideo.ts @@ -0,0 +1,118 @@ +/** + * 本地视频加载 Hook + * ================= + * + * 使用 Tauri fs.readFile 读取本地视频文件为 Uint8Array, + * 然后创建 Blob URL 供 video 标签使用。 + * 绕过 convertFileSrc/asset protocol 在 macOS 上的 404 问题。 + */ + +import { useState, useEffect, useRef } from 'react'; +import { readFile } from '@tauri-apps/plugin-fs'; +import { homeDir } from '@tauri-apps/api/path'; + +interface UseLocalVideoResult { + videoUrl: string | undefined; + isLoading: boolean; + error: string | null; +} + +/** + * 将 Docker 容器路径转换为宿主机路径 + * Docker 内 /root 映射到宿主机 home 目录 + */ +async function resolveHostPath(dockerPath: string): Promise { + if (dockerPath.startsWith('/root/')) { + const home = await homeDir(); + return dockerPath.replace(/^\/root/, home || '/Users'); + } + return dockerPath; +} + +/** + * 加载本地视频文件,返回 Blob URL + * + * @param filePath 本地文件绝对路径(如 /Users/.../scene_1.mp4) + * @returns Blob URL 或 undefined + */ +export function useLocalVideo(filePath: string | undefined): UseLocalVideoResult { + const [videoUrl, setVideoUrl] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const blobUrlRef = useRef(null); + + useEffect(() => { + // 清理旧的 Blob URL + if (blobUrlRef.current) { + URL.revokeObjectURL(blobUrlRef.current); + blobUrlRef.current = null; + } + + if (!filePath) { + setVideoUrl(undefined); + setIsLoading(false); + setError(null); + return; + } + + // 远程 URL 直接使用 + if (filePath.startsWith('http')) { + setVideoUrl(filePath); + setIsLoading(false); + setError(null); + return; + } + + let canceled = false; + + async function load() { + if (!filePath) return; + setIsLoading(true); + setError(null); + + try { + const hostPath = await resolveHostPath(filePath); + if (canceled) return; + + const data = await readFile(hostPath); + if (canceled) return; + + const blob = new Blob([data], { type: 'video/mp4' }); + const url = URL.createObjectURL(blob); + blobUrlRef.current = url; + + if (!canceled) { + setVideoUrl(url); + } + } catch (e) { + if (!canceled) { + console.error('[useLocalVideo] Failed to load video:', e); + setError(e instanceof Error ? e.message : '加载失败'); + setVideoUrl(undefined); + } + } finally { + if (!canceled) { + setIsLoading(false); + } + } + } + + load(); + + return () => { + canceled = true; + }; + }, [filePath]); + + // 组件卸载时释放 Blob URL + useEffect(() => { + return () => { + if (blobUrlRef.current) { + URL.revokeObjectURL(blobUrlRef.current); + blobUrlRef.current = null; + } + }; + }, []); + + return { videoUrl, isLoading, error }; +} diff --git a/tauri-app/src/hooks/useModelHealth.ts b/tauri-app/src/hooks/useModelHealth.ts new file mode 100644 index 0000000..d2aba41 --- /dev/null +++ b/tauri-app/src/hooks/useModelHealth.ts @@ -0,0 +1,37 @@ +/** + * Model Health Hook - SWR + * ======================= + * + * 模型健康状态监控 + */ + +import useSWR from 'swr'; +import { scriptApi, ModelHealthResponse } from '../api/modules/script'; + +const MODEL_HEALTH_KEY = 'model-health'; + +/** + * 获取模型健康状态 + * - 每 30 秒自动刷新 + * - 适合在设置页面展示 + */ +export function useModelHealth() { + const { data, error, isLoading, mutate } = useSWR( + MODEL_HEALTH_KEY, + () => scriptApi.checkModelHealth(), + { + refreshInterval: 30000, // 30 秒自动刷新 + revalidateOnFocus: false, + errorRetryCount: 2, + } + ); + + return { + health: data, + isLoading, + error, + mutate, + isHealthy: data?.status === 'healthy', + availableModels: data?.availableModels ?? 0, + }; +} diff --git a/tauri-app/src/hooks/usePerformanceMonitor.ts b/tauri-app/src/hooks/usePerformanceMonitor.ts new file mode 100644 index 0000000..926bffa --- /dev/null +++ b/tauri-app/src/hooks/usePerformanceMonitor.ts @@ -0,0 +1,86 @@ +/** + * Performance Monitor Hook + * ======================== + * + * 监控组件渲染性能,帮助发现性能问题 + * 仅在开发环境生效 + */ + +import { useEffect, useRef } from 'react'; + +interface PerformanceMetrics { + renderCount: number; + lastRenderTime: number; + averageRenderTime: number; +} + +/** + * 监控组件渲染性能 + * + * 使用方式: + * function MyComponent() { + * usePerformanceMonitor('MyComponent'); + * return
...
; + * } + */ +export function usePerformanceMonitor(componentName: string) { + // 仅在开发环境监控 + if (import.meta.env.PROD) { + return; + } + + const metricsRef = useRef({ + renderCount: 0, + lastRenderTime: 0, + averageRenderTime: 0, + }); + + const startTimeRef = useRef(0); + + useEffect(() => { + startTimeRef.current = performance.now(); + }); + + useEffect(() => { + const endTime = performance.now(); + const renderTime = endTime - startTimeRef.current; + + const metrics = metricsRef.current; + metrics.renderCount++; + metrics.lastRenderTime = renderTime; + metrics.averageRenderTime = + (metrics.averageRenderTime * (metrics.renderCount - 1) + renderTime) / metrics.renderCount; + + // 慢渲染警告(超过 16ms = 1帧) + if (renderTime > 16) { + console.warn( + `[Performance] ${componentName} rendered slowly: ${renderTime.toFixed(2)}ms ` + + `(avg: ${metrics.averageRenderTime.toFixed(2)}ms, count: ${metrics.renderCount})` + ); + } + }); +} + +/** + * 测量函数执行时间 + */ +export function measurePerformance any>(fn: T, name: string): T { + return function (...args: Parameters): ReturnType { + const start = performance.now(); + const result = fn(...args); + const end = performance.now(); + + console.log(`[Performance] ${name} took ${(end - start).toFixed(2)}ms`); + + return result; + } as T; +} + +/** + * 创建性能标记(用于 React DevTools Profiler) + */ +export function markRenderPhase(phase: string) { + if (import.meta.env.DEV && window.performance) { + performance.mark(`react-${phase}`); + } +} diff --git a/tauri-app/src/hooks/useSubtitleAlignment.ts b/tauri-app/src/hooks/useSubtitleAlignment.ts new file mode 100644 index 0000000..c06edc7 --- /dev/null +++ b/tauri-app/src/hooks/useSubtitleAlignment.ts @@ -0,0 +1,390 @@ +/** + * 字幕打轴 Hook(Async Engine 版) + * ================================ + * + * 管理字幕打轴流程,调用后端 Async Engine 统一任务 API。 + * 每个 shot 提交一个独立的 subtitle 任务(mode: auto_align)。 + * 打轴完成后,可以根据时间轴预览不同样式的字幕效果。 + * 打轴结果自动持久化到项目数据。 + */ + +import { useState, useCallback, useEffect, useRef } from 'react'; +import { useTask } from './useTask'; +import { useProgressStore } from '../store/progressStore'; +import type { AlignmentResult as StoreAlignmentResult } from '../api/types'; + +/** 字幕片段 */ +export interface Utterance { + text: string; + startTime: number; // 毫秒 + endTime: number; // 毫秒 +} + +/** 打轴状态 */ +export type AlignmentStatus = 'pending' | 'aligning' | 'completed' | 'failed'; + +/** 打轴结果 */ +export interface AlignmentResult { + shotId: number; + status: AlignmentStatus; + utterances?: Utterance[]; + duration?: number; + errorMessage?: string; +} + +/** 打轴请求参数 */ +interface AlignParams { + videoUrl: string; + audioText: string; +} + +/** 分镜数据(用于初始化恢复) */ +interface Segment { + id: number; + alignmentResult?: StoreAlignmentResult; +} + +/** 更新分镜数据的回调 */ +type UpdateSegmentFn = (id: number, data: Partial) => void; + +/** + * 字幕打轴 Hook + * + * @param segments 当前项目分镜列表(用于初始化恢复打轴结果) + * @param updateSegment 更新分镜数据的函数(用于持久化打轴结果) + */ +export function useSubtitleAlignment( + segments: Segment[] = [], + updateSegment?: UpdateSegmentFn +) { + const { submit } = useTask(); + const [results, setResults] = useState>({}); + const [isAligning, setIsAligning] = useState(false); + const [progress, setProgress] = useState({ + current: 0, + total: 0, + }); + + // 跟踪正在进行的任务数量 + const pendingCountRef = useRef(0); + // 跟踪已完成/失败的任务数量(用于弹窗进度) + const completedCountRef = useRef(0); + const failedCountRef = useRef(0); + + // 使用 ref 标记是否已初始化,避免依赖数组大小变化问题 + const hasInitializedRef = useRef(false); + const prevSegmentsLengthRef = useRef(0); + + /** + * 从 segments 恢复打轴结果(页面加载时调用) + */ + useEffect(() => { + const currentLength = segments.length; + const prevLength = prevSegmentsLengthRef.current; + + // 只有当 segments 从 0 变为有数据时才初始化 + if (currentLength > 0 && prevLength === 0 && !hasInitializedRef.current) { + hasInitializedRef.current = true; + + const restoredResults: Record = {}; + + segments.forEach(segment => { + if (segment.alignmentResult) { + restoredResults[segment.id] = { + shotId: segment.id, + status: segment.alignmentResult.status, + utterances: segment.alignmentResult.utterances?.map(u => ({ + text: u.text, + startTime: u.start_time, + endTime: u.end_time, + })), + duration: segment.alignmentResult.duration, + errorMessage: segment.alignmentResult.errorMessage, + }; + } + }); + + // 从持久化数据恢复内存状态(初始化时同步 setState 是合理的) + // eslint-disable-next-line react-hooks/set-state-in-effect + setResults(restoredResults); + } + + prevSegmentsLengthRef.current = currentLength; + }, [segments]); + + /** + * 保存打轴结果到 store + */ + const saveResultToStore = useCallback((shotId: number, result: AlignmentResult) => { + if (!updateSegment) { + return; + } + + const alignmentResult = { + status: result.status, + utterances: result.utterances?.map(u => ({ + text: u.text, + start_time: u.startTime, + end_time: u.endTime, + })), + duration: result.duration, + errorMessage: result.errorMessage, + }; + updateSegment(shotId, { alignmentResult }); + }, [updateSegment]); + + /** + * 单个分镜打轴(通过 Async Engine 提交异步任务) + */ + const alignSingle = useCallback(async ( + shotId: number, + params: AlignParams + ): Promise => { + if (!params.videoUrl) { + return null; + } + if (!params.audioText) { + return null; + } + + // 更新状态为打轴中 + setResults(prev => ({ ...prev, [shotId]: { shotId, status: 'aligning' } })); + + // 必须在 submit() 之前递增计数器。 + // submit() 内部出错时会同步调用 onError 回调(在返回 null 之前)。 + // 如果在这里不先递增,onError 中的递减会导致计数器变为负数, + // 进而使 alignBatch 的等待循环提前结束(isAligning 在任务仍在运行时变为 false)。 + pendingCountRef.current += 1; + + try { + const taskId = await submit( + 'subtitle', + { + videoPath: params.videoUrl, + audioText: params.audioText, + mode: 'auto_align', + language: 'zh', + }, + { + showProgress: false, + callbacks: { + onComplete: (result: unknown) => { + const r = result as { + utterances?: Array<{ text: string; startTime: number; endTime: number }>; + duration?: number; + } | undefined; + + const alignmentResult: AlignmentResult = { + shotId, + status: 'completed', + utterances: r?.utterances?.map(u => ({ + text: u.text, + startTime: u.startTime, + endTime: u.endTime, + })), + duration: r?.duration, + }; + setResults(prev => ({ ...prev, [shotId]: alignmentResult })); + saveResultToStore(shotId, alignmentResult); + + completedCountRef.current += 1; + const total = completedCountRef.current + failedCountRef.current + pendingCountRef.current - 1; + pendingCountRef.current -= 1; + if (pendingCountRef.current <= 0) { + pendingCountRef.current = 0; + setIsAligning(false); + } + + // 更新弹窗进度 + const done = completedCountRef.current + failedCountRef.current; + useProgressStore.getState().update( + `正在打轴... (${done}/${total > 0 ? total : done})` + ); + }, + onError: (error: string) => { + const alignmentResult: AlignmentResult = { + shotId, + status: 'failed', + errorMessage: error, + }; + setResults(prev => ({ ...prev, [shotId]: alignmentResult })); + saveResultToStore(shotId, alignmentResult); + + failedCountRef.current += 1; + const total = completedCountRef.current + failedCountRef.current + pendingCountRef.current - 1; + pendingCountRef.current -= 1; + if (pendingCountRef.current <= 0) { + pendingCountRef.current = 0; + setIsAligning(false); + } + + // 更新弹窗进度 + const done = completedCountRef.current + failedCountRef.current; + useProgressStore.getState().update( + `正在打轴... (${done}/${total > 0 ? total : done})` + ); + }, + }, + } + ); + + if (!taskId) { + // submit() 已内部处理错误并调用了 onError(递减过计数器), + // 计数器已恢复到正确值,无需额外操作。 + return null; + } + + return taskId; + } catch (error) { + // alignSingle 自身抛出的异常(理论上极少,因为 submit 已内部捕获) + failedCountRef.current += 1; + pendingCountRef.current -= 1; + if (pendingCountRef.current <= 0) { + pendingCountRef.current = 0; + setIsAligning(false); + } + + // 更新弹窗进度 + const done = completedCountRef.current + failedCountRef.current; + const total = done + pendingCountRef.current; + useProgressStore.getState().update( + `正在打轴... (${done}/${total > 0 ? total : done})` + ); + + const errorMessage = error instanceof Error ? error.message : '打轴失败'; + const result: AlignmentResult = { + shotId, + status: 'failed', + errorMessage, + }; + setResults(prev => ({ ...prev, [shotId]: result })); + saveResultToStore(shotId, result); + return null; + } + }, [submit, saveResultToStore]); + + /** + * 批量打轴 + * + * 并行提交所有 shot 的异步任务,等待所有任务完成后返回。 + * 调用方可以通过 await alignBatch() 确保所有打轴真正完成后再执行后续操作。 + */ + const alignBatch = useCallback(async ( + shots: Array<{ id: number; videoUrl?: string; audioText?: string }> + ) => { + if (shots.length === 0) { + return; + } + + const validShots = shots.filter(s => s.videoUrl && s.audioText); + if (validShots.length === 0) { + return; + } + + setIsAligning(true); + setProgress({ current: 0, total: validShots.length }); + pendingCountRef.current = 0; + completedCountRef.current = 0; + failedCountRef.current = 0; + + // 显示统一进度弹窗 + const total = validShots.length; + useProgressStore.getState().show('字幕打轴'); + + // 并行提交所有任务 + const submitPromises = validShots.map(async (shot, index) => { + const taskId = await alignSingle(shot.id, { + videoUrl: shot.videoUrl!, + audioText: shot.audioText!, + }); + setProgress({ current: index + 1, total: validShots.length }); + return taskId; + }); + + await Promise.all(submitPromises); + + // 等待所有任务真正完成(或失败) + if (pendingCountRef.current > 0) { + await new Promise((resolve) => { + const startTime = Date.now(); + const MAX_WAIT_MS = 5 * 60 * 1000; // 5 分钟安全超时 + const check = () => { + if (pendingCountRef.current <= 0) { + resolve(); + } else if (Date.now() - startTime > MAX_WAIT_MS) { + console.warn('[useSubtitleAlignment] alignBatch wait timeout'); + pendingCountRef.current = 0; + resolve(); + } else { + setTimeout(check, 300); + } + }; + check(); + }); + } + + // 弹窗显示最终结果 + const completed = completedCountRef.current; + const failed = failedCountRef.current; + if (failed > 0) { + useProgressStore.getState().error( + `打轴完成:${completed}/${total} 成功,${failed} 个失败` + ); + } else { + useProgressStore.getState().success('打轴完成'); + } + + setIsAligning(false); + setProgress({ current: 0, total: 0 }); + }, [alignSingle]); + + /** + * 获取当前时间对应的字幕文本 + */ + const getSubtitleAtTime = useCallback((shotId: number, currentTimeMs: number): string | null => { + const result = results[shotId]; + if (!result?.utterances) { + return null; + } + + const utterance = result.utterances.find( + u => currentTimeMs >= u.startTime && currentTimeMs <= u.endTime + ); + return utterance?.text || null; + }, [results]); + + /** + * 重置指定分镜的打轴结果 + */ + const resetShot = useCallback((shotId: number) => { + const result: AlignmentResult = { + shotId, + status: 'pending', + }; + setResults(prev => ({ ...prev, [shotId]: result })); + saveResultToStore(shotId, result); + }, [saveResultToStore]); + + /** + * 重置所有打轴结果 + */ + const resetAll = useCallback(() => { + setResults({}); + setProgress({ current: 0, total: 0 }); + // 注意:重置所有需要遍历所有 segments 清除 store 中的数据 + // 这里只清理内存状态,store 中的数据由调用方处理 + }, []); + + return { + results, + isAligning, + progress, + alignSingle, + alignBatch, + getSubtitleAtTime, + resetShot, + resetAll, + }; +} + +export default useSubtitleAlignment; diff --git a/tauri-app/src/hooks/useTask.ts b/tauri-app/src/hooks/useTask.ts new file mode 100644 index 0000000..b3afdfb --- /dev/null +++ b/tauri-app/src/hooks/useTask.ts @@ -0,0 +1,256 @@ +/** + * 统一任务管理 Hook + * ================= + * + * 封装任务提交、轮询、状态管理 + * 与 progressStore 联动显示进度弹窗 + */ + +import { useCallback, useRef, useEffect } from 'react'; +import { client } from '../api/client'; +import { useProgressStore } from '../store/progressStore'; +import { useTaskStore, TaskType, TaskStatus } from '../store/taskStore'; +import { listTasks } from '../api/modules/task'; +import { toast } from '../store/uiStore'; + +// 稳定的 store getter,避免 useCallback 依赖数组频繁重建 +const getProgress = () => useProgressStore.getState(); +const getTaskStore = () => useTaskStore.getState(); + +export interface TaskCallbacks { + onProgress?: (status: string, progress: number, message: string) => void; + onComplete?: (result: unknown) => void; + onError?: (error: string) => void; +} + +interface SubmitOptions { + projectId?: string; + showProgress?: boolean; + callbacks?: TaskCallbacks; +} + +// 轮询间隔配置 +const POLL_CONFIG = { + initialInterval: 1000, // 初始1秒 + maxInterval: 3000, // 封顶3秒 + backoffMultiplier: 1.5, +}; + +// 任务类型对应的显示标题 +const TASK_TITLES: Record = { + video: '视频生成', + image: '图片生成', + script: '脚本生成', + subtitle: '字幕对齐', + copy: '文案提取', + avatar_clone: '形象克隆', +}; + +export function useTask() { + const abortRef = useRef>(new Set()); + const intervalRef = useRef>(new Map()); + + // 清理函数 + useEffect(() => { + const abortSet = abortRef.current; + return () => { + abortSet.clear(); + }; + }, []); + + /** + * 开始轮询任务状态 + */ + const startPolling = useCallback( + (taskId: string, callbacks?: TaskCallbacks) => { + abortRef.current.delete(taskId); + intervalRef.current.set(taskId, POLL_CONFIG.initialInterval); + + const poll = async () => { + if (abortRef.current.has(taskId)) { + return; + } + + try { + const taskPath = `/tasks/${taskId}`; + const task = await client.get<{ + taskId: string; + type: TaskType; + status: TaskStatus; + progress: number; + message: string; + result?: unknown; + error?: string; + }>(taskPath); + + // 更新本地状态 + getTaskStore().updateTask(taskId, { + status: task.status, + progress: task.progress, + message: task.message, + }); + + // 更新进度弹窗 + const progressState = getProgress(); + if (progressState.visible) { + progressState.update(task.message); + } + + // 调用进度回调(失败状态优先用 error 字段,避免 message 还是旧文案) + const progressMessage = task.status === 'failed' && task.error ? task.error : task.message; + callbacks?.onProgress?.(task.status, task.progress, progressMessage); + + // 处理完成状态 + if (task.status === 'completed') { + getTaskStore().completeTask(taskId, task.result); + progressState.success('任务完成'); + callbacks?.onComplete?.(task.result); + intervalRef.current.delete(taskId); + return; + } + + // 处理失败状态 + if (task.status === 'failed') { + getTaskStore().failTask(taskId, task.error || '任务失败'); + progressState.error(task.error || '任务失败'); + callbacks?.onError?.(task.error || '任务失败'); + intervalRef.current.delete(taskId); + return; + } + + // 继续轮询 + const currentInterval = intervalRef.current.get(taskId) || POLL_CONFIG.initialInterval; + const nextInterval = Math.min( + currentInterval * POLL_CONFIG.backoffMultiplier, + POLL_CONFIG.maxInterval + ); + intervalRef.current.set(taskId, nextInterval); + + setTimeout(poll, nextInterval); + } catch { + setTimeout(poll, 3000); + } + }; + + poll(); + }, + [] + ); + + /** + * 提交任务 + */ + const submit = useCallback( + async ( + type: TaskType, + params: Record, + options: SubmitOptions = {} + ): Promise => { + const { projectId, showProgress = true, callbacks } = options; + + try { + const response = await client.post<{ taskId: string; status: string }>( + `/tasks/${type}`, + { + projectId, + params, + } + ); + + const taskId = response.taskId; + + getTaskStore().addTask({ + id: taskId, + type, + projectId, + status: 'pending', + progress: 0, + message: '任务已创建', + }); + + if (showProgress) { + getProgress().show(TASK_TITLES[type]); + } + + startPolling(taskId, callbacks); + + return taskId; + } catch (error) { + const message = error instanceof Error ? error.message : '创建任务失败'; + toast.error(message); + callbacks?.onError?.(message); + return null; + } + }, + [startPolling] + ); + + /** + * 从后端查询 running 任务并恢复轮询 + */ + const fetchRunningTasks = useCallback( + async (projectId?: string, callbacks?: TaskCallbacks) => { + try { + const tasks = await listTasks(projectId); + const runningTasks = tasks.filter( + (t) => t.status === 'running' || t.status === 'pending' + ); + for (const task of runningTasks) { + if (intervalRef.current.has(task.taskId)) continue; + + getTaskStore().addTask({ + id: task.taskId, + type: task.type, + projectId, + status: task.status, + progress: task.progress, + message: task.message, + }); + + startPolling(task.taskId, callbacks); + } + return runningTasks; + } catch (err) { + console.error('[useTask] Failed to fetch running tasks:', err); + return []; + } + }, + [startPolling] + ); + + /** + * 取消任务(仅停止轮询,后端任务继续运行) + */ + const cancel = useCallback((taskId: string) => { + abortRef.current.add(taskId); + intervalRef.current.delete(taskId); + }, []); + + /** + * 获取任务状态 + */ + const getTask = useCallback( + (taskId: string) => { + return getTaskStore().tasks.find((t) => t.id === taskId); + }, + [] + ); + + /** + * 检查是否有进行中的任务 + */ + const hasRunningTasks = useCallback(() => { + return getTaskStore().getRunningTasks().length > 0; + }, []); + + return { + submit, + startPolling, + fetchRunningTasks, + restoreTask: startPolling, + cancel, + getTask, + hasRunningTasks, + runningTasks: getTaskStore().getRunningTasks(), + }; +} diff --git a/tauri-app/src/hooks/useVideoGeneration.ts b/tauri-app/src/hooks/useVideoGeneration.ts new file mode 100644 index 0000000..7072090 --- /dev/null +++ b/tauri-app/src/hooks/useVideoGeneration.ts @@ -0,0 +1,319 @@ +import { useState, useCallback, useRef, useEffect } from 'react'; +import { useTask } from './useTask'; +import { useProgressStore } from '../store/progressStore'; + +export interface GenerationResult { + projectId: string; + completed: number; + failed: number; + total: number; + shots: Array<{ + shotId: string; + type: string; + status: string; + taskId: string | null; + videoUrl: string | null; + localPath: string | null; + qiniuUrl: string | null; + errorMessage: string | null; + }>; +} + +export interface VideoGenerationParams { + projectId?: string; + elementId?: number; + shots?: Array<{ + id: number | string; + type?: string; + scene?: string; + voiceover?: string; + duration?: string | number; + voice_id?: string; + }>; +} + +const TASK_TIMEOUT_MS = 60 * 60 * 1000; + +export function useVideoGeneration() { + const [isGenerating, setIsGenerating] = useState(false); + const [isRegenerating, setIsRegenerating] = useState(false); + const [error, setError] = useState(null); + const [result, setResult] = useState(null); + + const isActiveRef = useRef(true); + const abortRef = useRef<(() => void) | null>(null); + const timeoutRef = useRef | null>(null); + + const { submit, cancel } = useTask(); + + useEffect(() => { + return () => { + isActiveRef.current = false; + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + if (abortRef.current) { + abortRef.current(); + } + }; + }, []); + + const reset = useCallback(() => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + if (abortRef.current) { + abortRef.current(); + abortRef.current = null; + } + isActiveRef.current = true; + setIsGenerating(false); + setIsRegenerating(false); + setError(null); + setResult(null); + }, []); + + const startGeneration = useCallback( + async (params: VideoGenerationParams): Promise => { + if (!params.shots || params.shots.length === 0) { + setError('没有可生成的分镜'); + return null; + } + + isActiveRef.current = true; + setIsGenerating(true); + setError(null); + setResult(null); + + const shots = params.shots.map((shot) => ({ + id: shot.id, + type: shot.type || 'segment', + scene: shot.scene || '', + voiceover: shot.voiceover || '', + duration: shot.duration || '5s', + voice_id: shot.voice_id, + })); + + // 手动显示进度弹窗 + useProgressStore.getState().show('视频生成'); + + return new Promise((resolve) => { + let settled = false; + + const settle = (value: GenerationResult | null) => { + if (settled) return; + settled = true; + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + abortRef.current = null; + resolve(value); + }; + + timeoutRef.current = setTimeout(() => { + if (!isActiveRef.current) return; + setError('任务处理超时'); + setIsGenerating(false); + settle(null); + }, TASK_TIMEOUT_MS); + + const run = async () => { + try { + const taskId = await submit( + 'video', + { + shots, + element_id: params.elementId, + }, + { + projectId: params.projectId, + showProgress: false, + callbacks: { + onComplete: (taskResult) => { + if (!isActiveRef.current) return; + const data = taskResult as GenerationResult; + if (data.completed === 0 && data.failed > 0) { + const firstError = data.shots.find((s) => s.errorMessage)?.errorMessage || '视频生成失败'; + setError(firstError); + setIsGenerating(false); + settle(null); + return; + } + setResult(data); + setIsGenerating(false); + settle(data); + }, + onError: (errMessage: string) => { + if (!isActiveRef.current) return; + setError(errMessage); + setIsGenerating(false); + settle(null); + }, + }, + } + ); + + if (!taskId) { + setIsGenerating(false); + setError('创建任务失败'); + settle(null); + return; + } + + abortRef.current = () => { + cancel(taskId); + }; + } catch (err) { + const errorMsg = err instanceof Error ? err.message : '视频生成失败'; + setError(errorMsg); + setIsGenerating(false); + settle(null); + } + }; + + run(); + }); + }, + [submit, cancel] + ); + + const regenerateShot = useCallback( + async (params: { + projectId?: string; + elementId?: number; + shot: { + id: number | string; + type?: string; + scene?: string; + voiceover?: string; + duration?: string | number; + voice_id?: string; + }; + }): Promise => { + if (!params.shot) { + return null; + } + + isActiveRef.current = true; + setIsRegenerating(true); + setError(null); + + // 手动显示进度弹窗(单镜头用 percent) + useProgressStore.getState().show('视频生成'); + + return new Promise((resolve) => { + let settled = false; + + const settle = (value: GenerationResult['shots'][0] | null) => { + if (settled) return; + settled = true; + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + abortRef.current = null; + resolve(value); + }; + + timeoutRef.current = setTimeout(() => { + if (!isActiveRef.current) return; + setError('任务处理超时'); + setIsRegenerating(false); + settle(null); + }, TASK_TIMEOUT_MS); + + const run = async () => { + const shots = [{ + id: params.shot.id, + type: params.shot.type || 'segment', + scene: params.shot.scene || '', + voiceover: params.shot.voiceover || '', + duration: params.shot.duration || '5s', + voice_id: params.shot.voice_id, + }]; + + try { + const taskId = await submit( + 'video', + { + shots, + element_id: params.elementId, + }, + { + projectId: params.projectId, + showProgress: false, + callbacks: { + onComplete: (taskResult) => { + if (!isActiveRef.current) return; + const data = taskResult as GenerationResult; + const shotResult = data.shots[0]; + if (!shotResult || shotResult.status !== 'completed') { + const errorMsg = shotResult?.errorMessage || '视频生成失败'; + setError(errorMsg); + setIsRegenerating(false); + settle(null); + return; + } + setResult(data); + setIsRegenerating(false); + settle(shotResult); + }, + onError: (errMessage: string) => { + if (!isActiveRef.current) return; + setError(errMessage); + setIsRegenerating(false); + settle(null); + }, + }, + } + ); + + if (!taskId) { + setIsRegenerating(false); + setError('创建任务失败'); + settle(null); + return; + } + + abortRef.current = () => { + cancel(taskId); + }; + } catch (err) { + const errorMsg = err instanceof Error ? err.message : '视频生成失败'; + setError(errorMsg); + setIsRegenerating(false); + settle(null); + } + }; + + run(); + }); + }, + [submit, cancel] + ); + + const abort = useCallback(() => { + if (abortRef.current) { + abortRef.current(); + abortRef.current = null; + } + isActiveRef.current = false; + setIsGenerating(false); + setIsRegenerating(false); + }, []); + + return { + isGenerating, + isRegenerating, + error, + result, + startGeneration, + regenerateShot, + abort, + reset, + }; +} + +export default useVideoGeneration; diff --git a/tauri-app/src/main.tsx b/tauri-app/src/main.tsx new file mode 100644 index 0000000..3f30900 --- /dev/null +++ b/tauri-app/src/main.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import App from './App'; +import './styles/variables.css'; +import './styles/global.css'; + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + + + +); diff --git a/tauri-app/src/pages/ContentManagement/AvatarCard.tsx b/tauri-app/src/pages/ContentManagement/AvatarCard.tsx new file mode 100644 index 0000000..e5c5438 --- /dev/null +++ b/tauri-app/src/pages/ContentManagement/AvatarCard.tsx @@ -0,0 +1,236 @@ +import React, { useRef, useState, useEffect, useCallback } from 'react'; +import { AvatarItem } from '../../api/modules/avatar'; +import { useCachedVideoUrl, useCachedPosterUrl } from '../../hooks/useAvatarCache'; + +interface AvatarCardProps { + avatar: AvatarItem; + isEditing: boolean; + editName: string; + isSaving: boolean; + onEditChange: (val: string) => void; + onEditConfirm: () => void; + onEditCancel: () => void; + onStartEdit: (e: React.MouseEvent, avatar: AvatarItem) => void; + onDelete: (avatar: AvatarItem) => void; + formatDate: (dateStr?: string) => string; +} + +const AvatarCard: React.FC = ({ + avatar, + isEditing, + editName, + isSaving, + onEditChange, + onEditConfirm, + onEditCancel, + onStartEdit, + onDelete, + formatDate, +}) => { + const videoRef = useRef(null); + const [isPlaying, setIsPlaying] = useState(false); + + // 使用本地缓存 - 优先使用本地视频文件 + const { videoUrl: cachedVideoUrl, isCached } = useCachedVideoUrl( + avatar.id, + avatar.videoUrl + ); + + // 生成视频封面 + const generatePoster = useCallback((): Promise => { + return new Promise((resolve) => { + if (!cachedVideoUrl) { + resolve(''); + return; + } + + const video = document.createElement('video'); + // 本地文件不需要跨域 + if (!isCached) { + video.crossOrigin = 'anonymous'; + } + video.src = cachedVideoUrl; + video.muted = true; + video.playsInline = true; + + video.onloadeddata = () => { + video.currentTime = 0.1; + }; + + video.onseeked = () => { + const canvas = document.createElement('canvas'); + canvas.width = video.videoWidth || 300; + canvas.height = video.videoHeight || 400; + const ctx = canvas.getContext('2d'); + if (ctx) { + ctx.drawImage(video, 0, 0, canvas.width, canvas.height); + const poster = canvas.toDataURL('image/jpeg', 0.8); + resolve(poster); + } else { + resolve(''); + } + }; + + video.onerror = () => { + console.warn('[AvatarCard] Failed to load video for poster:', cachedVideoUrl); + resolve(''); + }; + + video.load(); + }); + }, [cachedVideoUrl, isCached]); + + // 使用封面缓存 - 如果有本地缓存直接使用 + const { posterUrl, isLoaded } = useCachedPosterUrl(avatar.id, generatePoster); + + // 播放/暂停切换 + const togglePlay = (e: React.MouseEvent) => { + e.stopPropagation(); + const video = videoRef.current; + if (!video) return; + + if (video.paused) { + video.play(); + setIsPlaying(true); + } else { + video.pause(); + setIsPlaying(false); + } + }; + + // 监听视频状态变化 + useEffect(() => { + const video = videoRef.current; + if (!video) return; + + const handlePlay = () => setIsPlaying(true); + const handlePause = () => setIsPlaying(false); + const handleEnded = () => setIsPlaying(false); + + video.addEventListener('play', handlePlay); + video.addEventListener('pause', handlePause); + video.addEventListener('ended', handleEnded); + + return () => { + video.removeEventListener('play', handlePlay); + video.removeEventListener('pause', handlePause); + video.removeEventListener('ended', handleEnded); + }; + }, []); + + return ( +
+
+ {cachedVideoUrl ? ( + <> + {!isLoaded && ( +
+ )} +
+ +
+ {isEditing ? ( + onEditChange(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') onEditConfirm(); + if (e.key === 'Escape') onEditCancel(); + }} + onBlur={() => onEditConfirm()} + autoFocus + disabled={isSaving} + /> + ) : ( +
+ {avatar.name} +
+ )} +
+ {formatDate(avatar.recordTime)} +
+
+
+ ); +}; + +export default AvatarCard; diff --git a/tauri-app/src/pages/ContentManagement/AvatarClone.tsx b/tauri-app/src/pages/ContentManagement/AvatarClone.tsx new file mode 100644 index 0000000..ef0867b --- /dev/null +++ b/tauri-app/src/pages/ContentManagement/AvatarClone.tsx @@ -0,0 +1,274 @@ +import { useState, useEffect } from 'react'; +import './ContentManagement.css'; +import type { AvatarItem } from '../../api/modules/avatar'; +import ConfirmModal from '../../components/Modal/ConfirmModal'; +import AvatarUploadModal from '../../components/Modal/AvatarUploadModal'; +import { useAvatarLibrary } from '../../hooks/useAvatarLibrary'; +import { deleteAvatarCache } from '../../hooks/useAvatarCache'; +import { + loadClonedAvatarsFromLocal, + saveClonedAvatarsToLocal, + removeClonedAvatarFromLocal, +} from '../../utils/avatarStorage'; +import AvatarCard from './AvatarCard'; + +function formatDate(dateStr?: string) { + if (!dateStr) { + return ''; + } + try { + const date = new Date(dateStr); + if (isNaN(date.getTime())) { + return dateStr; + } + const pad = (n: number) => n.toString().padStart(2, '0'); + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`; + } catch { + return dateStr; + } +} + +export default function AvatarClone() { + const [search, setSearch] = useState(''); + + const [showUploadModal, setShowUploadModal] = useState(false); + + const { avatarLibrary, mutate: refreshAvatarLibrary } = useAvatarLibrary(); + + // 编辑状态 + const [editingAvatar, setEditingAvatar] = useState(null); + const [editName, setEditName] = useState(''); + const [isSaving, setIsSaving] = useState(false); + + // 删除确认弹窗状态 + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [deletingAvatar, setDeletingAvatar] = useState(null); + const [isDeleting, setIsDeleting] = useState(false); + + // 组件加载时刷新数据 + useEffect(() => { + localStorage.removeItem('swr-avatar-library'); + refreshAvatarLibrary(); + }, [refreshAvatarLibrary]); + + const clonedAvatars = avatarLibrary.filter(a => a.elementId && a.elementId > 0); + const filtered = clonedAvatars.filter(a => a.name.toLowerCase().includes(search.toLowerCase())); + + // 分页 + const [currentPage, setCurrentPage] = useState(1); + const pageSize = 10; + const totalPages = Math.ceil(filtered.length / pageSize); + const paginatedAvatars = filtered.slice((currentPage - 1) * pageSize, currentPage * pageSize); + + // 搜索时重置分页 + useEffect(() => { + setCurrentPage(1); + }, [search]); + + // ========== 编辑(重命名 - 行内) ========== + const startInlineEdit = (e: React.MouseEvent, avatar: AvatarItem) => { + e.stopPropagation(); + setEditingAvatar(avatar); + setEditName(avatar.name); + }; + + const confirmRename = async () => { + if (!editingAvatar) { + return; + } + + const newName = editName.trim(); + if (!newName) { + return; + } + + if (newName === editingAvatar.name) { + setEditingAvatar(null); + return; + } + + setIsSaving(true); + try { + // 仅更新本地 + const clonedAvatars = await loadClonedAvatarsFromLocal(); + const updated = clonedAvatars.map(a => + a.id === editingAvatar.id ? { ...a, name: newName } : a + ); + await saveClonedAvatarsToLocal(updated); + + setEditingAvatar(null); + await refreshAvatarLibrary(); + } catch (e: any) { + } finally { + setIsSaving(false); + } + }; + + // ========== 删除 ========== + const openDeleteModal = (avatar: AvatarItem) => { + setDeletingAvatar(avatar); + setShowDeleteModal(true); + }; + + const confirmDelete = async () => { + if (!deletingAvatar) { + return; + } + setIsDeleting(true); + + // 删除本地文件缓存 + try { + await deleteAvatarCache(deletingAvatar.id); + console.log('[AvatarClone] Local cache deleted for:', deletingAvatar.id); + } catch (e) { + console.warn('[AvatarClone] Failed to delete local cache:', e); + // 忽略缓存删除失败,不影响主流程 + } + + removeClonedAvatarFromLocal(deletingAvatar.id); + + setShowDeleteModal(false); + setDeletingAvatar(null); + await refreshAvatarLibrary(); + setIsDeleting(false); + }; + + // ========== 上传成功回调 ========== + const handleUploadSuccess = () => { + // 刷新库列表(useAvatarLibrary 会自动合并本地和后端数据) + refreshAvatarLibrary(); + setShowUploadModal(false); + }; + + return ( +
+
+

形象克隆

+
+
+ + + + + setSearch(e.target.value)} + /> +
+ +
+
+ +
+ {paginatedAvatars.length > 0 ? ( + paginatedAvatars.map(avatar => ( + setEditingAvatar(null)} + onStartEdit={startInlineEdit} + onDelete={openDeleteModal} + formatDate={formatDate} + /> + )) + ) : ( +
+
+ + + + +
+

暂无克隆形象

+

点击"上传视频"添加素材

+
+ )} +
+ + {/* 分页 */} + {totalPages > 1 && ( +
+
+ + {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => ( + + ))} + +
+
+ )} + + {/* 上传弹窗 */} + setShowUploadModal(false)} + onSuccess={handleUploadSuccess} + /> + + {/* 删除确认弹窗 */} + 确认删除形象 「{deletingAvatar.name}」 吗? : ''} + description="此操作不可撤销,本地记录将同时被清除" + confirmText={isDeleting ? '删除中...' : '确认删除'} + cancelText="取消" + confirmButtonType="danger" + onConfirm={confirmDelete} + onCancel={() => !isDeleting && setShowDeleteModal(false)} + /> +
+ ); +} diff --git a/tauri-app/src/pages/ContentManagement/ContentManagement.css b/tauri-app/src/pages/ContentManagement/ContentManagement.css new file mode 100644 index 0000000..b627a58 --- /dev/null +++ b/tauri-app/src/pages/ContentManagement/ContentManagement.css @@ -0,0 +1,1284 @@ +.content-page { + display: flex; + flex-direction: column; + gap: var(--spacing-xl); + height: 100%; +} + +.content-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing-lg); +} + +.content-header h2 { + font-size: var(--font-xl); +} + +.content-search { + display: flex; + align-items: center; + gap: var(--spacing-md); + flex: 1; + max-width: 400px; +} + +.content-search .btn { + height: 40px; +} + +.search-input-wrapper { + position: relative; + flex: 1; +} + +.search-input-wrapper svg { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: var(--text-tertiary); + pointer-events: none; +} + +.search-input-wrapper input { + padding-left: 38px; + height: 40px; +} + +.content-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(310px, 1fr)); + gap: var(--spacing-lg); +} + +.voice-card { + display: flex; + align-items: center; + gap: var(--spacing-md); + padding: var(--spacing-lg) var(--spacing-md); +} + +.voice-avatar { + width: 48px; + height: 48px; + border-radius: var(--radius-full); + background: var(--primary-gradient); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: white; +} + +.voice-info { + flex: 1; + min-width: 0; +} + +.voice-name { + font-weight: 600; + font-size: var(--font-base); + color: var(--text-primary); + margin-bottom: var(--spacing-xs); +} + +/* 行内编辑输入框 */ +.voice-name-input { + padding: var(--spacing-xs) var(--spacing-sm); + border: 2px solid var(--primary); + border-radius: var(--radius-md); + font-size: var(--font-base); + font-weight: 600; + font-family: inherit; + background: var(--bg-card); + color: var(--text-primary); + width: 100px; + max-width: 100%; + outline: none; + transition: all var(--transition-fast); + box-shadow: 0 0 0 3px var(--primary-light); +} + +.voice-name-input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 4px var(--primary-light); +} + +.voice-name-input:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.voice-meta { + font-size: var(--font-xs); + color: var(--text-tertiary); +} + +.voice-actions { + display: flex; + gap: var(--spacing-xs); +} + +/* Digital Human Card */ +.dh-card { + overflow: hidden; +} + +.dh-preview { + height: 180px; + background: var(--bg-input); + display: flex; + align-items: center; + justify-content: center; + color: var(--text-tertiary); + position: relative; +} + +.dh-preview-overlay { + position: absolute; + inset: 0; + background: rgb(0 0 0 / 40%); + display: flex; + align-items: center; + justify-content: center; + gap: var(--spacing-sm); + opacity: 0; + transition: opacity var(--transition-fast); +} + +.dh-card:hover .dh-preview-overlay { + opacity: 1; +} + +.dh-card-info { + padding: var(--spacing-md) var(--spacing-lg); + display: flex; + align-items: center; + justify-content: space-between; +} + +.dh-card-name { + font-weight: 500; + font-size: var(--font-sm); + color: var(--text-primary); +} + +/* Works Card */ +.works-tabs { + display: flex; + gap: var(--spacing-xs); + border-bottom: 1px solid var(--border-light); + padding-bottom: 0; +} + +.works-tab { + padding: var(--spacing-sm) var(--spacing-lg); + font-size: var(--font-sm); + font-family: var(--font-family); + color: var(--text-tertiary); + background: none; + border: none; + cursor: pointer; + border-bottom: 2px solid transparent; + transition: all var(--transition-fast); + margin-bottom: -1px; +} + +.works-tab:hover { + color: var(--text-primary); +} + +.works-tab.active { + color: var(--primary); + border-bottom-color: var(--primary); + font-weight: 500; +} + +.work-card { + overflow: hidden; + cursor: pointer; +} + +.work-card:hover { + transform: translateY(-2px); +} + +.work-thumb { + height: 160px; + background: var(--bg-input); + display: flex; + align-items: center; + justify-content: center; + color: var(--text-tertiary); + position: relative; +} + +.work-duration { + position: absolute; + bottom: var(--spacing-sm); + right: var(--spacing-sm); + background: rgb(0 0 0 / 60%); + color: white; + font-size: var(--font-xs); + padding: var(--spacing-2xs) var(--spacing-xs); + border-radius: var(--radius-sm); +} + +.work-info { + padding: var(--spacing-md) var(--spacing-lg); +} + +.work-title { + font-size: var(--font-sm); + font-weight: 500; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Settings Page */ +.settings-page { + width: 100%; + display: flex; + flex-direction: column; + gap: var(--spacing-xl); +} + +.settings-section { + display: flex; + flex-direction: column; + gap: var(--spacing-md); +} + +.settings-section h2 { + font-size: var(--font-xl); +} + +.settings-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-lg); + background: var(--bg-card); + border-radius: var(--radius-lg); + border: 1px solid var(--border-light); +} + +.settings-row-label { + font-size: var(--font-sm); + color: var(--text-secondary); +} + +.settings-row-value { + font-size: var(--font-sm); + font-weight: 500; + color: var(--text-primary); +} + +/* Profile Page */ +.profile-page { + max-width: 640px; + display: flex; + flex-direction: column; + gap: var(--spacing-xl); +} + +.profile-header { + display: flex; + align-items: center; + gap: var(--spacing-xl); + padding: var(--spacing-2xl); + background: var(--bg-card); + border-radius: var(--radius-xl); + border: 1px solid var(--border-light); +} + +.profile-avatar { + width: 72px; + height: 72px; + border-radius: var(--radius-full); + background: var(--primary-gradient); + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: var(--font-2xl); + font-weight: 700; + flex-shrink: 0; +} + +.profile-name { + font-size: var(--font-xl); + font-weight: 600; +} + +.profile-role { + font-size: var(--font-sm); + color: var(--text-tertiary); +} + +/* Avatar Clone Card - 这个就是我们要复用的样式 */ +.avatar-card { + position: relative; + background: var(--bg-card); + border-radius: var(--radius-xl); + border: 1px solid var(--border-light); + padding: var(--spacing-sm); + transition: all var(--transition-normal) cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + flex-direction: column; + gap: var(--spacing-md); + cursor: default; + align-self: start; +} + +.avatar-card:hover { + transform: translateY(-4px); + border-color: var(--primary-light); + box-shadow: 0 12px 24px -8px rgba(0, 0, 0, 0.12); +} + +.avatar-card-thumb-container { + width: 100%; + aspect-ratio: 3/4; + border-radius: var(--radius-lg); + overflow: hidden; + background: var(--bg-input); + position: relative; + z-index: 1; +} + +.avatar-card-video { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform var(--transition-normal) ease, opacity 0.3s ease; + background: var(--bg-input); + display: block; +} + +/* 居中播放按钮 - 默认隐藏,悬停显示 */ +.avatar-card-thumb-container .avatar-card-play-btn { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.25); + backdrop-filter: blur(8px); + border: 1px solid rgba(255, 255, 255, 0.4); + color: white; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0 !important; + transition: opacity 0.2s ease, transform 0.2s ease, background 0.2s ease; + z-index: 10; + padding: 0; +} + +/* 鼠标悬停显示按钮 */ +.avatar-card-thumb-container:hover .avatar-card-play-btn, +.avatar-card-thumb-container.playing .avatar-card-play-btn { + opacity: 1 !important; +} + +.avatar-card-thumb-container .avatar-card-play-btn:hover { + background: rgba(255, 255, 255, 0.4); + transform: translate(-50%, -50%) scale(1.1); +} + +.avatar-card-thumb-container .avatar-card-play-btn:active { + transform: translate(-50%, -50%) scale(0.95); +} + +/* 视频加载中状态 */ +.avatar-card-video-loading { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, var(--bg-input) 0%, var(--bg-hover) 100%); + color: var(--text-tertiary); + font-size: var(--font-sm); +} + +.avatar-card-video-loading::after { + content: ''; + width: 24px; + height: 24px; + border: 2px solid var(--border-color); + border-top-color: var(--primary-color); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.avatar-card:hover .avatar-card-video { + transform: scale(1.05); +} + +.avatar-card-placeholder { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--spacing-md); + color: var(--text-tertiary); + background: linear-gradient(135deg, var(--bg-input) 0%, var(--bg-hover) 100%); +} + +.avatar-card-placeholder-icon { + width: 64px; + height: 64px; + border-radius: var(--radius-full); + background: rgb(255 255 255 / 50%); + display: flex; + align-items: center; + justify-content: center; + color: var(--text-tertiary); + backdrop-filter: blur(4px); + border: 1px solid rgb(255 255 255 / 30%); +} + +.avatar-card-placeholder span { + font-size: var(--font-xs); + font-weight: 500; + letter-spacing: 0.5px; +} + +.avatar-card-actions { + position: absolute; + top: var(--spacing-sm); + right: var(--spacing-sm); + display: flex; + gap: var(--spacing-sm); + opacity: 0; + transform: translateY(-8px); + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 10; +} + +.avatar-card:hover .avatar-card-actions { + opacity: 1; + transform: translateY(0); +} + +.avatar-card-action-btn { + width: 28px; + height: 28px; + border-radius: var(--radius-full); + background: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(8px); + border: 1px solid rgba(255, 255, 255, 0.2); + color: white; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all var(--transition-fast); +} + +.avatar-card-action-btn:hover { + background: var(--primary); + border-color: var(--primary); + transform: scale(1.1); +} + +.avatar-card-action-btn.delete:hover { + background: var(--danger); + border-color: var(--danger); +} + +.avatar-card-info { + padding: 0 var(--spacing-xs) var(--spacing-xs); + min-width: 0; +} + +.avatar-card-name { + font-size: var(--font-sm); + font-weight: 600; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 4px; +} + +.avatar-card-date { + font-size: var(--font-xs); + color: var(--text-tertiary); +} + +.avatar-card-name-input { + width: 100%; + font-size: var(--font-sm); + font-weight: 600; + color: var(--text-primary); + background: var(--bg-input); + border: 1px solid var(--primary); + border-radius: var(--radius-sm); + padding: 0 var(--spacing-xs); + height: 24px; + line-height: 24px; + outline: none; + margin-bottom: 4px; +} + +.avatar-card-play-overlay { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.2); + color: white; + opacity: 0; + transition: opacity var(--transition-normal); + pointer-events: none; + z-index: 5; +} + +.avatar-card:hover .avatar-card-play-overlay, +.avatar-card.playing .avatar-card-play-overlay { + opacity: 1; +} + +.avatar-card-thumb-container { + cursor: pointer; +} + +.avatar-card.playing { + border-color: var(--primary); + box-shadow: 0 0 0 2px var(--primary-light), 0 12px 24px -8px rgba(0, 0, 0, 0.12); +} + +.avatar-card.playing .avatar-card-video { + transform: scale(1.05); +} + +/* Avatar Grid Layout */ +.avatar-grid { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: var(--spacing-xl); + padding: var(--spacing-sm) var(--spacing-xs); + flex: 1; +} + +/* Content Page Compact variant */ +.content-page-compact { + gap: var(--spacing-md); +} + +/* Pagination Container */ +.pagination-container { + display: flex; + justify-content: center; + margin-top: 8px; +} + +/* Empty State Full Width */ +.empty-state-full { + grid-column: 1 / -1; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +/* Delete Confirmation Modal - 已迁移到 ConfirmModal 组件 */ + +/* Usage Detail - 使用明细表格 */ +.usage-filter-bar { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--spacing-md); + gap: var(--spacing-md); +} + +.usage-filter-bar .filter-item { + display: flex; + align-items: center; + gap: var(--spacing-md); +} + +.usage-filter-bar .filter-label { + font-size: var(--font-sm); + color: var(--text-secondary); + white-space: nowrap; +} + +.usage-filter-bar .filter-input-group { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.usage-filter-bar .filter-input-box { + padding: var(--spacing-sm) var(--spacing-md); + background: var(--bg-input); + border: 1px solid var(--border-light); + border-radius: var(--radius-lg); + font-size: var(--font-sm); + color: var(--text-primary); + min-width: 180px; +} + +.usage-filter-bar .filter-separator { + color: var(--text-tertiary); + font-size: var(--font-sm); +} + +.usage-table { + width: 100%; + border-collapse: collapse; +} + +.usage-table thead tr { + background: var(--bg-secondary); + border-bottom: 2px solid var(--border-light); +} + +.usage-table th { + padding: var(--spacing-md) var(--spacing-md); + text-align: left; + font-size: var(--font-sm); + font-weight: 600; + color: var(--text-secondary); + white-space: nowrap; +} + +.usage-table tbody tr { + border-bottom: 1px solid var(--border-light); +} + +.usage-table tbody tr:hover { + background: var(--bg-hover); +} + +.usage-table td { + padding: var(--spacing-md) var(--spacing-md); + font-size: var(--font-sm); + color: var(--text-primary); +} + +/* End Usage Detail */ + +/* ============================================================ + 成品卡片样式 - 完全复用 avatar-card 风格 + ============================================================ */ + +/* Products Grid - 和 avatar-grid 完全一致 */ +.products-grid { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: var(--spacing-xl); + padding: var(--spacing-sm) var(--spacing-xs); + flex: 1; +} + +/* 成品卡片 - 和 avatar-card 完全一样 */ +.product-card { + position: relative; + background: var(--bg-card); + border-radius: var(--radius-xl); + border: 1px solid var(--border-light); + padding: var(--spacing-sm); + transition: all var(--transition-normal) cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + flex-direction: column; + gap: var(--spacing-md); + cursor: pointer; + align-self: start; +} + +.product-card:hover { + transform: translateY(-4px); + border-color: var(--primary-light); + box-shadow: 0 12px 24px -8px rgba(0, 0, 0, 0.12); +} + +/* 成品视频容器 - 9:16 比例(视频尺寸) */ +.product-video-container { + width: 100%; + aspect-ratio: 9 / 16; + border-radius: var(--radius-lg); + overflow: hidden; + background: var(--bg-input); + position: relative !important; + z-index: 1; +} + +.product-video { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform var(--transition-normal) ease, opacity 0.3s ease; + background: var(--bg-input); + display: block; +} + +.product-card:hover .product-video { + transform: scale(1.05); +} + +/* 操作按钮 - 和 avatar-card-actions 位置样式一致 */ +.product-overlay { + position: absolute; + top: var(--spacing-sm); + right: var(--spacing-sm); + opacity: 0; + transform: translateY(-8px); + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 10; + display: flex; + gap: var(--spacing-xs); +} + +.product-card:hover .product-overlay { + opacity: 1; + transform: translateY(0); +} + +.product-action-btn { + width: 28px; + height: 28px; + border-radius: var(--radius-full); + background: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(8px); + border: 1px solid rgba(255, 255, 255, 0.2); + color: white; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all var(--transition-fast); +} + +.product-action-btn:hover { + transform: scale(1.1); +} + +.product-rename-btn:hover { + background: var(--primary); + border-color: var(--primary); +} + +.product-delete-btn:hover { + background: var(--danger); + border-color: var(--danger); +} + +/* 重命名编辑浮层 */ +.rename-overlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.4); + display: flex; + align-items: center; + justify-content: center; + padding: var(--spacing-md); + z-index: 15; + border-radius: var(--radius-lg); +} + +.rename-form { + display: flex; + gap: var(--spacing-xs); + align-items: center; + background: var(--bg-card); + padding: var(--spacing-sm); + border-radius: var(--radius-lg); + border: 1px solid var(--primary); + width: 100%; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); +} + +.rename-form input { + flex: 1; + padding: var(--spacing-xs) var(--spacing-sm); + font-size: var(--font-sm); + background: var(--bg-input); + border: none; + border-radius: var(--radius-md); + outline: none; + color: var(--text-primary); +} + +.rename-form .save-btn, +.rename-form .cancel-btn { + width: 32px; + height: 32px; + border-radius: var(--radius-full); + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + transition: all var(--transition-fast); +} + +.rename-form .save-btn { + background: var(--primary); + color: white; +} + +.rename-form .save-btn:hover { + transform: scale(1.1); +} + +.rename-form .cancel-btn { + background: var(--bg-input); + color: var(--text-primary); +} + +.rename-form .cancel-btn:hover { + transform: scale(1.1); +} + +.product-info { + padding: 0 var(--spacing-xs) var(--spacing-xs); + min-width: 0; +} + +.product-name { + font-size: var(--font-sm); + font-weight: 600; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 4px; +} + +.product-meta { + font-size: var(--font-xs); + color: var(--text-tertiary); + display: flex; + gap: 4px; +} + +.product-meta .separator { + opacity: 0.5; +} + +/* ============================================================ + 草稿箱列表样式 - 文字列表,无缩略图 + ============================================================ */ + +.drafts-list-container { + flex: 1; +} + +.drafts-list { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.draft-list-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-md) var(--spacing-lg); + background: var(--bg-card); + border: 1px solid var(--border-light); + border-radius: var(--radius-lg); + cursor: pointer; + transition: all var(--transition-fast); +} + +.draft-list-item:hover { + border-color: var(--primary-light); + background: var(--bg-hover); + transform: translateX(2px); +} + +.draft-info { + flex: 1; + min-width: 0; +} + +.draft-title { + font-size: var(--font-base); + font-weight: 500; + color: var(--text-primary); + margin-bottom: 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.draft-meta { + display: flex; + align-items: center; + gap: var(--spacing-md); + font-size: var(--font-xs); + color: var(--text-tertiary); +} + +.draft-topic { + max-width: 300px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + opacity: 0.8; +} + +.draft-arrow { + flex-shrink: 0; + color: var(--text-tertiary); + opacity: 0.5; +} + +.draft-list-item:hover .draft-arrow { + opacity: 0.8; +} + +/* ============================================================ + 我的作品 - 全新样式 + ============================================================ */ + +/* Tab 栏 - Pill 样式 */ +.works-tabs-bar { + display: inline-flex; + align-items: center; + gap: var(--spacing-xs); + padding: var(--spacing-xs); + background: var(--bg-input); + border-radius: var(--radius-lg); + width: fit-content; +} + +.works-tab-pill { + display: inline-flex; + align-items: center; + gap: var(--spacing-xs); + padding: var(--spacing-sm) var(--spacing-lg); + font-size: var(--font-sm); + font-family: var(--font-family); + color: var(--text-secondary); + background: transparent; + border: none; + border-radius: var(--radius-md); + cursor: pointer; + transition: all 0.2s ease; + font-weight: 500; +} + +.works-tab-pill:hover { + color: var(--text-primary); +} + +.works-tab-pill.active { + background: var(--primary); + color: white; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.works-tab-count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + padding: 0 6px; + font-size: var(--font-xs); + font-weight: 600; + background: rgba(0, 0, 0, 0.06); + border-radius: var(--radius-full); +} + +.works-tab-pill.active .works-tab-count { + background: rgba(255, 255, 255, 0.25); +} + +/* 网格布局 - 一行4个 */ +.works-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: var(--spacing-lg); + flex: 1; + align-content: start; + align-items: start; +} + +/* 成品卡片 */ +.works-card { + position: relative; + background: #ffffff; + border-radius: var(--radius-xl); + border: 1px solid var(--border-light); + padding: var(--spacing-sm); + overflow: hidden; + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; + display: flex; + flex-direction: column; +} + +.works-card:hover { + transform: translateY(-2px); + border-color: var(--primary-light); + box-shadow: 0 8px 24px -6px rgba(0, 0, 0, 0.1); +} + +.works-card.playing { + border-color: var(--primary); + box-shadow: 0 0 0 2px var(--primary-light), 0 8px 24px -6px rgba(0, 0, 0, 0.1); +} + +/* 封面区域 - 9:16 比例 */ +.works-card-cover { + position: relative; + width: 100%; + aspect-ratio: 9 / 16; + background: var(--bg-input); + border-radius: var(--radius-lg); + overflow: hidden; + flex-shrink: 0; +} + +.works-card-poster { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 2; +} + +.works-card-video { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +/* 播放按钮 */ +.works-card-play-btn { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 36px; + height: 36px; + border-radius: 50%; + background: rgba(0, 0, 0, 0.45); + backdrop-filter: blur(4px); + border: 1px solid rgba(255, 255, 255, 0.25); + color: white; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0; + transition: all 0.2s ease; + z-index: 5; + padding: 0; +} + +.works-card:hover .works-card-play-btn, +.works-card.playing .works-card-play-btn { + opacity: 1; +} + +.works-card-play-btn:hover { + background: rgba(0, 0, 0, 0.6); + transform: translate(-50%, -50%) scale(1.1); +} + +/* 加载状态 */ +.works-card-loading { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-input); + z-index: 3; +} + +.works-card-spinner { + width: 20px; + height: 20px; + border: 2px solid var(--border-light); + border-top-color: var(--primary); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +/* 占位 */ +.works-card-placeholder { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-tertiary); +} + +/* 操作按钮 */ +.works-card-overlay { + position: absolute; + top: var(--spacing-sm); + right: var(--spacing-sm); + display: flex; + gap: var(--spacing-xs); + opacity: 0; + transform: translateY(-4px); + transition: all 0.2s ease; + z-index: 10; +} + +.works-card:hover .works-card-overlay { + opacity: 1; + transform: translateY(0); +} + +.works-card-action { + width: 28px; + height: 28px; + border-radius: var(--radius-full); + background: rgba(0, 0, 0, 0.45); + backdrop-filter: blur(4px); + border: 1px solid rgba(255, 255, 255, 0.2); + color: white; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.15s ease; + padding: 0; +} + +.works-card-action:hover { + background: var(--primary); + border-color: var(--primary); + transform: scale(1.1); +} + +.works-card-action.delete:hover { + background: var(--danger); + border-color: var(--danger); +} + +/* 信息区域 */ +.works-card-info { + padding: var(--spacing-md) var(--spacing-lg); +} + +.works-card-name { + font-size: var(--font-sm); + font-weight: 500; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 4px; +} + +.works-card-meta { + font-size: var(--font-xs); + color: var(--text-tertiary); +} + +/* 重命名 */ +.works-card-rename { + display: flex; + align-items: center; + gap: var(--spacing-xs); + margin-bottom: 4px; +} + +.works-card-rename-input { + flex: 1; + min-width: 0; + padding: var(--spacing-xs) var(--spacing-sm); + font-size: var(--font-sm); + font-weight: 500; + color: var(--text-primary); + background: var(--bg-input); + border: 1px solid var(--primary); + border-radius: var(--radius-md); + outline: none; + font-family: inherit; +} + +.works-card-rename-ext { + font-size: var(--font-sm); + color: var(--text-tertiary); + font-weight: 500; + flex-shrink: 0; +} + +/* 分页 - 与 VideoGeneration 统一 */ +.pagination { + display: inline-flex; + align-items: center; + gap: var(--spacing-xs); + padding: var(--spacing-xs); + background: var(--bg-input); + border-radius: var(--radius-lg); +} + +.pagination-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + font-size: var(--font-sm); + font-weight: 500; + color: var(--text-secondary); + background: transparent; + border: none; + border-radius: var(--radius-md); + cursor: pointer; + transition: all 0.2s ease; + flex-shrink: 0; +} + +.pagination-btn:hover:not(:disabled) { + background: var(--bg-card); + color: var(--text-primary); +} + +.pagination-btn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.pagination-btn.active { + background: var(--primary); + color: white; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.pagination-btn.nav { + width: auto; + padding: 0 var(--spacing-sm); + font-size: var(--font-xs); +} diff --git a/tauri-app/src/pages/ContentManagement/MyWorks.tsx b/tauri-app/src/pages/ContentManagement/MyWorks.tsx new file mode 100644 index 0000000..ee929ec --- /dev/null +++ b/tauri-app/src/pages/ContentManagement/MyWorks.tsx @@ -0,0 +1,360 @@ +import { useState, useEffect, useRef } from 'react'; +import { invoke } from '@tauri-apps/api/core'; +import { openPath } from '@tauri-apps/plugin-opener'; +import { readFile } from '@tauri-apps/plugin-fs'; +import { useNavigation } from '../../App'; +import { switchProject } from '../../store/projectStore'; +import ConfirmModal from '../../components/Modal/ConfirmModal'; +import { useLocalVideo } from '../../hooks/useLocalVideo'; +import type { ApiResponse } from '../../api/types'; +import './ContentManagement.css'; + +interface LocalProjectMeta { + id: string; + title: string; + topic?: string; + status: 'draft' | 'published'; + createdAt: number; + updatedAt: number; + coverPath?: string; + finalVideoPath?: string; +} + +interface ProductItem { + filename: string; + path: string; + created_at: number; + file_size: number; + poster_path?: string; +} + +interface DraftItem { + id: string; + title: string; + createdAt: number; + topic?: string; + updatedAt: number; +} + +type TabType = 'products' | 'drafts'; + +const tabs = [ + { id: 'products' as TabType, label: '成片' }, + { id: 'drafts' as TabType, label: '草稿箱' }, +]; + +const PAGE_SIZE = 8; + +function formatDateFriendly(timestamp: number): string { + const date = new Date(timestamp); + const now = new Date(); + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const target = new Date(date.getFullYear(), date.getMonth(), date.getDate()); + const diffDays = Math.round((today.getTime() - target.getTime()) / (1000 * 60 * 60 * 24)); + if (diffDays === 0) return '今天'; + if (diffDays === 1) return '昨天'; + if (diffDays < 7) return `${diffDays} 天前`; + return date.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }); +} + +function formatFileSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; + return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`; +} + +function getBaseName(filename: string): string { + const lastDot = filename.lastIndexOf('.'); + return lastDot > 0 ? filename.slice(0, lastDot) : filename; +} + +function getExtension(filename: string): string { + const lastDot = filename.lastIndexOf('.'); + return lastDot > 0 ? filename.slice(lastDot) : ''; +} + +function ProductCard({ product, onDelete, onRename }: { + product: ProductItem; + onDelete: (product: ProductItem) => void; + onRename: (product: ProductItem, newName: string) => void; +}) { + const videoRef = useRef(null); + const [isPlaying, setIsPlaying] = useState(false); + const [isRenaming, setIsRenaming] = useState(false); + const [editName, setEditName] = useState(getBaseName(product.filename)); + const [coverUrl, setCoverUrl] = useState(''); + const [isCoverLoading, setIsCoverLoading] = useState(false); + const { videoUrl } = useLocalVideo(product.path); + + useEffect(() => { + if (!product.poster_path) { + setCoverUrl(''); + setIsCoverLoading(false); + return; + } + let canceled = false; + let blobUrl: string | null = null; + async function load() { + setIsCoverLoading(true); + try { + const data = await readFile(product.poster_path!); + if (canceled) return; + const blob = new Blob([data], { type: 'image/jpeg' }); + blobUrl = URL.createObjectURL(blob); + setCoverUrl(blobUrl); + } catch (e) { + if (!canceled) setCoverUrl(''); + } finally { + if (!canceled) setIsCoverLoading(false); + } + } + load(); + return () => { + canceled = true; + if (blobUrl) URL.revokeObjectURL(blobUrl); + }; + }, [product.poster_path]); + + useEffect(() => { + const video = videoRef.current; + if (!video) return; + const handlePlay = () => setIsPlaying(true); + const handlePause = () => setIsPlaying(false); + const handleEnded = () => setIsPlaying(false); + video.addEventListener('play', handlePlay); + video.addEventListener('pause', handlePause); + video.addEventListener('ended', handleEnded); + return () => { + video.removeEventListener('play', handlePlay); + video.removeEventListener('pause', handlePause); + video.removeEventListener('ended', handleEnded); + }; + }, []); + + const togglePlay = (e: React.MouseEvent) => { + e.stopPropagation(); + const video = videoRef.current; + if (!video) return; + if (video.paused) { video.play(); setIsPlaying(true); } + else { video.pause(); setIsPlaying(false); } + }; + + const handleOpen = () => openPath(product.path); + + const startRename = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsRenaming(true); + setEditName(getBaseName(product.filename)); + }; + + const confirmRename = () => { + const base = editName.trim(); + if (!base) return; + const fullName = base + getExtension(product.filename); + if (fullName === product.filename) { setIsRenaming(false); return; } + onRename(product, fullName); + setIsRenaming(false); + }; + + const cancelRename = () => { + setIsRenaming(false); + setEditName(getBaseName(product.filename)); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') confirmRename(); + if (e.key === 'Escape') cancelRename(); + }; + + return ( +
+
+ {videoUrl ? ( + <> + {isCoverLoading &&
} + {coverUrl && !isPlaying && } +
+
+ {isRenaming ? ( +
+ setEditName(e.target.value)} onKeyDown={handleKeyDown} onBlur={confirmRename} autoFocus /> + {getExtension(product.filename)} +
+ ) : ( +
{product.filename}
+ )} +
{formatDateFriendly(product.created_at)} · {formatFileSize(product.file_size)}
+
+
+ ); +} + +function DraftListItem({ draft, onClick }: { draft: DraftItem; onClick: (id: string) => void }) { + return ( +
onClick(draft.id)}> +
+
{draft.title}
+
+ {draft.topic && {draft.topic}} + {formatDateFriendly(draft.createdAt)} +
+
+ +
+ ); +} + +export default function MyWorks() { + const [activeTab, setActiveTab] = useState('products'); + const [products, setProducts] = useState([]); + const [drafts, setDrafts] = useState([]); + const [loading, setLoading] = useState(true); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [deletingProduct, setDeletingProduct] = useState(null); + const [isDeleting, setIsDeleting] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const { navigate } = useNavigation(); + + const loadProducts = async () => { + try { + const result = await invoke('list_local_products') as ApiResponse; + setProducts(result.data || []); + } catch (error) { + console.error('[MyWorks] Failed to load products:', error); + } + }; + + const loadDrafts = async () => { + try { + const result = await invoke('list_local_projects') as ApiResponse; + const data = result.data || []; + const draftItems: DraftItem[] = data.map(meta => ({ id: meta.id, title: meta.title || meta.id, createdAt: meta.createdAt, updatedAt: meta.updatedAt, topic: meta.topic })); + draftItems.sort((a, b) => b.updatedAt - a.updatedAt); + setDrafts(draftItems); + } catch (error) { + console.error('[MyWorks] Failed to load drafts:', error); + } + }; + + useEffect(() => { async function loadAll() { await Promise.all([loadProducts(), loadDrafts()]); setLoading(false); } loadAll(); }, []); + useEffect(() => { setCurrentPage(1); }, [activeTab]); + + const handleRenameProduct = async (product: ProductItem, newFilename: string) => { + await invoke('rename_local_product', { old_filename: product.filename, new_filename: newFilename }) as ApiResponse; + await loadProducts(); + }; + + const openDeleteModal = (product: ProductItem) => { setDeletingProduct(product); setShowDeleteModal(true); }; + + const confirmDelete = async () => { + if (!deletingProduct) return; + setIsDeleting(true); + await invoke('delete_local_product', { filename: deletingProduct.filename }) as ApiResponse; + await loadProducts(); + setShowDeleteModal(false); + setDeletingProduct(null); + setIsDeleting(false); + }; + + const handleOpenDraft = async (draftId: string) => { await switchProject(draftId); navigate('video-creation'); }; + + const totalPages = Math.ceil(products.length / PAGE_SIZE); + const paginatedProducts = products.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE); + + return ( +
+

我的作品

+ +
+ {tabs.map(tab => ( + + ))} +
+ + {loading ? ( +

加载中...

+ ) : ( + <> + {activeTab === 'products' && ( + <> +
+ {paginatedProducts.length > 0 ? ( + paginatedProducts.map(product => ( + + )) + ) : ( +
+
+

暂无成片

+

完成视频合成后会保存在这里

+
+ )} +
+ {totalPages > 1 && ( +
+
+ + {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => ( + + ))} + +
+
+ )} + + )} + {activeTab === 'drafts' && ( +
+ {drafts.length > 0 ? ( +
+ {drafts.map(draft => )} +
+ ) : ( +
+
+

暂无草稿

+

开始创作后会保存草稿在这里

+
+ )} +
+ )} + + )} + + 确认删除成品 「{deletingProduct.filename}」 吗? : ''} + description="此操作不可撤销,文件将从本地磁盘删除" + confirmText={isDeleting ? '删除中...' : '确认删除'} + cancelText="取消" + confirmButtonType="danger" + onConfirm={confirmDelete} + onCancel={() => !isDeleting && setShowDeleteModal(false)} + /> +
+ ); +} diff --git a/tauri-app/src/pages/Login/Login.css b/tauri-app/src/pages/Login/Login.css new file mode 100644 index 0000000..6ea8005 --- /dev/null +++ b/tauri-app/src/pages/Login/Login.css @@ -0,0 +1,350 @@ +.login-page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-main); + position: relative; + overflow: hidden; +} + +/* 背景装饰 */ +.login-page::before { + content: ''; + position: absolute; + top: -30%; + right: -10%; + width: 600px; + height: 600px; + border-radius: 50%; + background: radial-gradient(circle, rgb(54 178 106 / 8%) 0%, transparent 70%); + pointer-events: none; +} + +.login-page::after { + content: ''; + position: absolute; + bottom: -20%; + left: -10%; + width: 500px; + height: 500px; + border-radius: 50%; + background: radial-gradient(circle, rgb(24 160 138 / 6%) 0%, transparent 70%); + pointer-events: none; +} + +.login-container { + width: 100%; + max-width: 420px; + padding: var(--spacing-2xl); + position: relative; + z-index: 1; +} + +/* Logo & 品牌 */ +.login-brand { + text-align: center; + margin-bottom: var(--spacing-2xl); +} + +.login-logo { + display: inline-flex; + align-items: center; + gap: var(--spacing-md); + margin-bottom: var(--spacing-lg); +} + +.login-logo svg { + flex-shrink: 0; +} + +.login-logo-text { + font-size: var(--font-xl); + font-weight: 700; + background: var(--primary-gradient); + background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + letter-spacing: 0.5px; +} + +.login-subtitle { + color: var(--text-tertiary); + font-size: var(--font-sm); +} + +/* 登录卡片 */ +.login-card { + background: var(--bg-card); + border-radius: var(--radius-xl); + border: 1px solid var(--border-light); + padding: var(--spacing-2xl); + box-shadow: var(--shadow-lg); + backdrop-filter: blur(10px); +} + +.login-card h2 { + font-size: var(--font-xl); + font-weight: 600; + margin-bottom: var(--spacing-xs); +} + +.login-card-desc { + color: var(--text-tertiary); + font-size: var(--font-sm); + margin-bottom: var(--spacing-xl); +} + +/* 表单 */ +.login-form { + display: flex; + flex-direction: column; + gap: var(--spacing-lg); +} + +.form-field { + display: flex; + flex-direction: column; + gap: var(--spacing-xs); +} + +.form-label { + font-size: var(--font-sm); + font-weight: 500; + color: var(--text-secondary); +} + +.phone-input-group { + display: flex; + align-items: center; + border: 1px solid var(--border-light); + border-radius: var(--radius-lg); + overflow: hidden; + transition: border-color var(--transition-fast); + background: var(--bg-input); +} + +.phone-input-group:focus-within { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgb(54 178 106 / 10%); +} + +.phone-prefix { + padding: 0 var(--spacing-md); + font-size: var(--font-base); + font-weight: 600; + color: var(--text-primary); + border-right: 1px solid var(--border-light); + display: flex; + align-items: center; + height: 48px; + flex-shrink: 0; + user-select: none; +} + +.phone-input-group input { + border: none; + background: transparent; + flex: 1; + height: 48px; + padding: 0 var(--spacing-md); + font-size: var(--font-base); + font-weight: 600; + letter-spacing: 2px; + outline: none; + color: var(--text-primary); +} + +.phone-input-group input::placeholder { + letter-spacing: normal; + font-weight: 400; + color: var(--text-placeholder); +} + +.code-input-group { + display: flex; + gap: var(--spacing-sm); +} + +.code-input-group input { + flex: 1; + height: 48px; + border-radius: var(--radius-lg); + border: 1px solid var(--border-light); + padding: 0 var(--spacing-md); + font-size: var(--font-base); + background: var(--bg-input); + color: var(--text-primary); + transition: border-color var(--transition-fast); + letter-spacing: 4px; + font-weight: 600; +} + +.code-input-group input:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgb(54 178 106 / 10%); + outline: none; +} + +.code-input-group input::placeholder { + letter-spacing: normal; + font-weight: 400; + color: var(--text-placeholder); +} + +.send-code-btn { + flex-shrink: 0; + height: 48px; + padding: 0 var(--spacing-lg); + border: 1px solid var(--primary); + border-radius: var(--radius-lg); + background: transparent; + color: var(--primary); + font-size: var(--font-sm); + font-weight: 500; + font-family: var(--font-family); + cursor: pointer; + transition: all var(--transition-fast); + white-space: nowrap; +} + +.send-code-btn:hover:not(:disabled) { + background: rgb(54 178 106 / 8%); +} + +.send-code-btn:disabled { + border-color: var(--border-light); + color: var(--text-tertiary); + cursor: not-allowed; +} + +/* 登录按钮 */ +.login-btn { + width: 100%; + height: 48px; + border: none; + border-radius: var(--radius-lg); + background: var(--primary-gradient); + color: white; + font-size: var(--font-base); + font-weight: 600; + font-family: var(--font-family); + cursor: pointer; + transition: all var(--transition-normal); + position: relative; + overflow: hidden; + margin-top: var(--spacing-sm); +} + +.login-btn::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(135deg, rgb(255 255 255 / 15%) 0%, transparent 50%); + opacity: 0; + transition: opacity var(--transition-fast); +} + +.login-btn:hover:not(:disabled)::before { + opacity: 1; +} + +.login-btn:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 16px rgb(54 178 106 / 35%); +} + +.login-btn:active:not(:disabled) { + transform: translateY(0); +} + +.login-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +.login-btn-loading { + display: inline-flex; + align-items: center; + gap: var(--spacing-sm); +} + +/* 协议 */ +.login-agreement { + display: flex; + align-items: center; + gap: var(--spacing-sm); + margin-top: var(--spacing-xs); +} + +.login-agreement input[type='checkbox'] { + appearance: none; + appearance: none; + box-sizing: border-box; + width: 18px; + height: 18px; + min-width: 18px; + min-height: 18px; + padding: 0; + margin: 0; + aspect-ratio: 1 / 1; + border: 1px solid var(--border-color, #dcdfe6); + border-radius: 50%; + cursor: pointer; + flex-shrink: 0; + position: relative; + background-color: var(--bg-card, #fff); + transition: all var(--transition-normal, 0.3s cubic-bezier(0.4, 0, 0.2, 1)); +} + +.login-agreement input[type='checkbox']:hover { + border-color: var(--primary); +} + +.login-agreement input[type='checkbox']:checked { + background-color: var(--primary); + border-color: var(--primary); + + /* SVG checkmark perfectly matching the rounded, thick reference */ + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E"); + background-size: 70%; + background-position: center; + background-repeat: no-repeat; + animation: checkbox-pop 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); +} + +@keyframes checkbox-pop { + 0% { + transform: scale(0.8); + } + + 100% { + transform: scale(1); + } +} + +.login-agreement label { + font-size: var(--font-xs); + color: var(--text-tertiary); + line-height: 1.5; + cursor: pointer; +} + +.login-agreement a { + color: var(--primary); + text-decoration: none; +} + +.login-agreement a:hover { + text-decoration: underline; +} + +/* 底部 */ +.login-footer { + text-align: center; + margin-top: var(--spacing-xl); + color: var(--text-placeholder); + font-size: var(--font-xs); +} diff --git a/tauri-app/src/pages/Login/Login.tsx b/tauri-app/src/pages/Login/Login.tsx new file mode 100644 index 0000000..6ab56ac --- /dev/null +++ b/tauri-app/src/pages/Login/Login.tsx @@ -0,0 +1,207 @@ +import { useState, useEffect, useCallback } from 'react'; +import { useAuthStore } from '../../store'; +import { toast } from '../../store/uiStore'; +import './Login.css'; + +/** + * 登录页面 + * + * 改进: + * - 使用全局 authStore 替代局部的 onLoginSuccess 回调 + * - 使用新的 uiStore toast 方法 + */ +export default function Login() { + const { login, isLoading } = useAuthStore(); + const [phone, setPhone] = useState(''); + const [code, setCode] = useState(''); + const [agreed, setAgreed] = useState(false); + const [countdown, setCountdown] = useState(0); + const [sending, setSending] = useState(false); + const [logging, setLogging] = useState(false); + + // 倒计时逻辑(必须放在所有条件返回之前,遵循 Hooks 规则) + useEffect(() => { + if (countdown <= 0) { + return; + } + const timer = setTimeout(() => setCountdown(c => c - 1), 1000); + return () => clearTimeout(timer); + }, [countdown]); + + // 手机号格式验证 + const isPhoneValid = /^1[3-9]\d{9}$/.test(phone); + const isCodeValid = /^\d{4,6}$/.test(code); + const canLogin = isPhoneValid && isCodeValid && agreed && !logging; + + // 发送验证码 + const handleSendCode = useCallback(() => { + if (!isPhoneValid || countdown > 0 || sending) { + return; + } + setSending(true); + // 模拟发送 + setTimeout(() => { + setSending(false); + setCountdown(60); + toast.success('验证码已发送'); + }, 800); + }, [isPhoneValid, countdown, sending]); + + // 登录 + const handleLogin = async () => { + if (!canLogin) { + return; + } + + setLogging(true); + try { + await login(phone, code); + // 登录成功后 authStore 会自动更新全局状态 + // App.tsx 会检测到 isAuthenticated 变化并切换界面 + toast.success('登录成功'); + } catch (error: any) { + toast.error(error.message || '登录失败,请重试'); + } finally { + setLogging(false); + } + }; + + // 回车键登录 + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && canLogin) { + handleLogin(); + } + }; + + return ( +
+
+ {/* 品牌 */} +
+
+ 美家卡 智影 + 美家卡 智影 +
+

AI 驱动的智能视频创作平台

+
+ + {/* 登录卡片 */} +
+

欢迎登录

+

使用手机号验证码快速登录

+ + {/* 加载状态 */} + {isLoading ? ( +
+ + + +

加载中...

+
+ ) : ( +
+ )} +
+ +
meijiaka.cn
+
+
+ ); +} diff --git a/tauri-app/src/pages/Profile/Profile.tsx b/tauri-app/src/pages/Profile/Profile.tsx new file mode 100644 index 0000000..05b9388 --- /dev/null +++ b/tauri-app/src/pages/Profile/Profile.tsx @@ -0,0 +1,64 @@ +import { useNavigation } from '../../App'; +import '../ContentManagement/ContentManagement.css'; + +export default function Profile() { + const { navigate } = useNavigation(); + + return ( +
+
+
U
+
+
用户
+
免费版用户
+
+
+ +
+

个人信息

+
+
+ 昵称 + + 用户 + + +
+ +
+ 绑定手机 + 138****8888 +
+ +
+ 剩余额度 + + 100 次 + + +
+
+
+ +
+

使用记录

+
+
+ 使用明细 +
+ +
+
+
+
+
+ ); +} diff --git a/tauri-app/src/pages/Profile/UsageDetail.tsx b/tauri-app/src/pages/Profile/UsageDetail.tsx new file mode 100644 index 0000000..ec43e41 --- /dev/null +++ b/tauri-app/src/pages/Profile/UsageDetail.tsx @@ -0,0 +1,137 @@ +import { useState } from 'react'; +import '../ContentManagement/ContentManagement.css'; + +interface UsageRecord { + id: string; + packageName: string; + packageId: string; + mode: string; + deductTime: string; + beforeQuota: number; + deductAmount: number; + afterQuota: number; +} + +// 模拟数据 +const mockData: UsageRecord[] = [ + { + id: '1', + packageName: '试用-视频生成-1000', + packageId: '869351574496624657', + mode: '4K画质 有声 音色', + deductTime: '2026-04-15 16:29:19', + beforeQuota: 512.4, + deductAmount: 6, + afterQuota: 506.4, + }, + { + id: '2', + packageName: '试用-视频生成-1000', + packageId: '869351574496624657', + mode: '4K画质 有声', + deductTime: '2026-04-15 16:28:16', + beforeQuota: 517.4, + deductAmount: 5, + afterQuota: 512.4, + }, + { + id: '3', + packageName: '试用-视频生成-1000', + packageId: '869351574496624657', + mode: '4K画质 有声', + deductTime: '2026-04-15 16:28:09', + beforeQuota: 522.4, + deductAmount: 5, + afterQuota: 517.4, + }, + { + id: '4', + packageName: '试用-图像生成-1000', + packageId: '871455826052554831', + mode: '', + deductTime: '2026-04-15 16:26:54', + beforeQuota: 816, + deductAmount: 8, + afterQuota: 808, + }, + { + id: '5', + packageName: '试用-视频生成-1000', + packageId: '869351574496624657', + mode: '4K画质 有声 音色', + deductTime: '2026-04-15 16:22:16', + beforeQuota: 528.4, + deductAmount: 6, + afterQuota: 522.4, + }, +]; + +export default function UsageDetail() { + const [startDate] = useState('2026-03-17 00:00:00'); + const [endDate] = useState('2026-04-15 23:59:59'); + + const handleExport = () => { + // 导出数据功能占位 + console.log('Export data...'); + }; + + return ( +
+
+

使用明细

+ + {/* 筛选区 */} +
+
+ 创建时间 +
+ {startDate} + To + {endDate} +
+
+ +
+ + {/* 使用明细表 */} +
+ + + + + + + + + + + + + + {mockData.map(record => ( + + + + + + + + + + ))} + +
资源包名称资源包ID模式抵扣时间 + + 抵扣前余量(次)抵扣量(次)抵扣后余量(次)
{record.packageName}{record.packageId}{record.mode}{record.deductTime}{record.beforeQuota}{record.deductAmount}{record.afterQuota}
+
+
+
+ ); +} diff --git a/tauri-app/src/pages/Settings/AboutUs.tsx b/tauri-app/src/pages/Settings/AboutUs.tsx new file mode 100644 index 0000000..e42f3d5 --- /dev/null +++ b/tauri-app/src/pages/Settings/AboutUs.tsx @@ -0,0 +1,44 @@ +import '../ContentManagement/ContentManagement.css'; + +export default function AboutUs() { + return ( +
+
+

关于我们

+
+
+ 应用名称 + 美家卡 智影 +
+
+ 版本号 + V 1.0.2 +
+
+
+ +
+

授权信息

+
+

+ 本软件由美家卡团队开发维护。授权用户可在授权范围内使用本软件进行视频创作。 + 如需商业授权或有任何疑问,请联系我们的支持团队。 +

+
+
+ +
+

版权声明

+
+

+ Copyright 2025 美家卡 (meijiaka.cn). All rights reserved. +

+

+ 本软件及其相关文档的所有权利均归美家卡所有。 + 未经授权,不得复制、修改、分发或以其他方式使用本软件。 +

+
+
+
+ ); +} diff --git a/tauri-app/src/pages/Settings/SystemUpdate.tsx b/tauri-app/src/pages/Settings/SystemUpdate.tsx new file mode 100644 index 0000000..a61a6b1 --- /dev/null +++ b/tauri-app/src/pages/Settings/SystemUpdate.tsx @@ -0,0 +1,62 @@ +import { useState } from 'react'; +import '../ContentManagement/ContentManagement.css'; + +export default function SystemUpdate() { + const [checking, setChecking] = useState(false); + const [checked, setChecked] = useState(false); + + const handleCheck = () => { + setChecking(true); + setChecked(false); + setTimeout(() => { + setChecking(false); + setChecked(true); + }, 2000); + }; + + return ( +
+
+

系统更新

+
+
+ 当前版本 + V 1.0.2 +
+
+ 版本更新 +
+ {checking ? ( + + + + + 检查中... + + ) : checked ? ( + 当前已是最新版本 + ) : ( + 未检查 + )} + +
+
+
+
+
+ ); +} diff --git a/tauri-app/src/pages/Settings/ThemeSettings.tsx b/tauri-app/src/pages/Settings/ThemeSettings.tsx new file mode 100644 index 0000000..2ab53f1 --- /dev/null +++ b/tauri-app/src/pages/Settings/ThemeSettings.tsx @@ -0,0 +1,192 @@ +import type { ReactNode } from 'react'; +import { useSettingsStore, type ThemeMode } from '../../store'; +import '../ContentManagement/ContentManagement.css'; + +const themeOptions: { id: ThemeMode; label: string; desc: string; icon: ReactNode }[] = [ + { + id: 'system', + label: '跟随系统', + desc: '自动匹配系统深浅色设置', + icon: ( + + + + + + ), + }, + { + id: 'dark', + label: '深色模式', + desc: '暗色背景,减少视觉疲劳', + icon: ( + + + + ), + }, + { + id: 'light', + label: '浅色模式', + desc: '明亮背景,清晰舒适', + icon: ( + + + + + + + + + + + + ), + }, +]; + +/** + * 主题设置页面 + * + * 改进: + * - 使用全局 settingsStore 替代局部的 localStorage 操作 + * - 主题变更会自动应用到全局并持久化 + */ +export default function ThemeSettings() { + const { theme, setTheme, isDark } = useSettingsStore(); + + return ( +
+
+

主题设置

+

+ 选择界面显示主题 +

+ +
+
+ {themeOptions.map((opt, index) => ( +
setTheme(opt.id)} + > +
+ {opt.icon} +
+
+
+ {opt.label} +
+
+ {opt.desc} +
+
+
+
+ ))} +
+
+ + {/* 当前状态提示 */} +
+ 当前模式: + + {theme === 'system' ? '跟随系统' : theme === 'dark' ? '深色模式' : '浅色模式'} + + {theme === 'system' && (实际显示:{isDark ? '深色' : '浅色'})} +
+
+
+ ); +} diff --git a/tauri-app/src/pages/VideoCreation/CoverDesign.css b/tauri-app/src/pages/VideoCreation/CoverDesign.css new file mode 100644 index 0000000..23f2f26 --- /dev/null +++ b/tauri-app/src/pages/VideoCreation/CoverDesign.css @@ -0,0 +1,373 @@ +/** + * 封面制作页面样式 + * ================= + * + * 完全遵循字幕压制页面结构和间距,保持一致 + */ + +/* 布局 - 55:45 和字幕压制一致 */ +.step-layout.subtitle-burning { + grid-template-columns: 55fr 45fr; +} + +.step-layout.cover-design-variant { + grid-template-columns: 55fr 45fr; +} + +/* 左侧操作区 - 与视频生成页面统一 */ +.cover-design .step-panel-left { + display: flex; + flex-direction: column; + gap: var(--spacing-lg); + overflow-y: auto; + overflow-x: hidden; + padding-right: var(--spacing-lg); + height: 100%; +} + +/* panel-section - 和字幕压制完全一致 */ +.cover-design .panel-section { + display: flex; + flex-direction: column; + gap: var(--spacing-md); +} + +/* 标题输入区域 */ +.title-input-section { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.title-input-section .input { + width: 100%; + padding: var(--spacing-sm) var(--spacing-md); + font-size: var(--font-base); + border: 1px solid var(--border-light); + border-radius: var(--radius-md); + background: var(--bg-card); + color: var(--text-primary); + transition: all var(--transition-fast); +} + +.title-input-section .input:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 2px rgb(54 178 106 / 10%); +} + +/* 模板标题区域头部 */ +.post-edit-template-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--spacing-sm); +} + +.post-edit-template-header .post-edit-section-title { + display: flex; + flex-direction: column; + gap: 2px; +} + +.panel-label { + font-size: var(--font-sm); + font-weight: 600; + color: var(--text-primary); +} + +.panel-sublabel { + font-size: var(--font-xs); + color: var(--text-tertiary); + font-weight: normal; +} + +/* 滑块分组 */ +.slider-group { + display: flex; + flex-direction: column; + gap: 4px; +} + +.slider-header { + display: flex; + justify-content: space-between; +} + +.slider-label { + font-size: var(--font-xs); + color: var(--text-secondary); +} + +/* 滑块输入样式 */ +.slider-input { + appearance: none; + width: 100%; + height: 6px; + background: transparent; + border-radius: var(--radius-sm); + outline: none; + cursor: pointer; + border: none; + padding: 0; +} + +.slider-input::-webkit-slider-runnable-track { + width: 100%; + height: 6px; + background: linear-gradient( + to right, + var(--primary) 0%, + var(--primary) var(--slider-percent, 50%), + var(--bg-input) var(--slider-percent, 50%), + var(--bg-input) 100% + ); + border-radius: var(--radius-sm); +} + +.slider-input::-webkit-slider-thumb { + appearance: none; + width: 18px; + height: 18px; + background: var(--bg-card); + border: 2px solid var(--primary); + border-radius: var(--radius-full); + cursor: pointer; + box-shadow: 0 1px 4px rgb(0 0 0 / 15%); + transition: + transform 0.15s ease, + box-shadow 0.15s ease; +} + +.slider-input::-webkit-slider-thumb:hover { + transform: scale(1.15); + box-shadow: 0 2px 8px rgb(54 178 106 / 30%); +} + +.slider-input:focus::-webkit-slider-thumb { + box-shadow: 0 0 0 4px var(--primary-light); +} + +/* 标题模板网格 - 和字幕压制预设样式完全一致 */ +.cover-design .style-presets { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: var(--spacing-sm); + margin-bottom: var(--spacing-sm); +} + +.preset-btn { + display: flex; + flex: 1; + flex-direction: column; + align-items: center; + gap: var(--spacing-xs); + padding: var(--spacing-sm) var(--spacing-md); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background: var(--bg-card); + cursor: pointer; + transition: all var(--transition-fast); +} + +.preset-btn:hover { + border-color: var(--primary); + background: var(--bg-hover); +} + +.preset-btn.active { + border-color: var(--primary); + background: var(--primary-light); + color: var(--primary); +} + +.preset-preview { + font-size: var(--font-lg); + font-weight: bold; + line-height: 1; +} + +.preset-name { + font-size: var(--font-xs); + color: var(--text-secondary); +} + +.preset-btn.active .preset-name { + color: var(--primary); +} + +/* 字号区域 - 和字幕压制一致 */ +.font-size-section { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); + margin-bottom: var(--spacing-sm); +} + +.font-size-value { + margin-left: var(--spacing-sm); + font-size: var(--font-sm); + color: var(--primary); + font-weight: 600; +} + +/* 封面图片选项 - 简洁双列卡片 */ +.cover-bg-options { + display: flex; + gap: var(--spacing-sm); +} + +.cover-bg-option { + flex: 1; + padding: var(--spacing-md) var(--spacing-sm); + border-radius: var(--radius-lg); + border: 1px solid var(--border-light); + background: var(--bg-card); + cursor: pointer; + text-align: center; + font-size: var(--font-sm); + color: var(--text-secondary); + transition: all var(--transition-fast); +} + +.cover-bg-option:hover { + border-color: var(--primary); + background: var(--bg-hover); + color: var(--primary); +} + +.cover-bg-option.selected { + border-color: var(--primary); + background: var(--primary-light); + color: var(--primary); + font-weight: 500; +} + +/* 提示文字 */ +.cover-hint { + margin-top: var(--spacing-sm); + font-size: var(--font-xs); + line-height: 1.5; +} + +.cover-hint-error { + color: #ef4444; +} + +.cover-hint-muted { + color: var(--text-tertiary); +} + +/* 生成按钮 */ +.cover-generate-btn { + width: 100%; + height: 48px; + font-size: var(--font-md); + font-weight: 600; + margin-top: var(--spacing-md); + display: flex; + align-items: center; + justify-content: center; + gap: var(--spacing-sm); +} + +/* 右侧预览区 - 与视频生成页面统一 */ +.video-gen-right { + display: flex; + flex-direction: column; + gap: var(--spacing-lg); + padding: 0; + overflow-y: auto; + align-items: center; + border-left: 1px solid var(--border-light); + padding-left: var(--spacing-lg); + min-height: 0; +} + +.video-preview-wrapper { + position: relative; + display: flex; + align-items: center; + justify-content: center; +} + +.video-preview-container { + position: relative; + width: auto; + height: 600px; + aspect-ratio: 9 / 16; + margin: 0 auto; + background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%); + border-radius: var(--radius-xl); + overflow: hidden; + box-shadow: 0 8px 32px rgb(0 0 0 / 30%); + border: 1px solid var(--border-light); +} + +.video-placeholder { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: rgba(255, 255, 255, 0.6); + gap: var(--spacing-sm); +} + +.video-placeholder .placeholder-sub { + font-size: var(--font-xs); + color: rgba(255, 255, 255, 0.4); +} + +/* 手机框预览 - 和字幕压制一致 */ +.post-edit-phone.cover-phone { + height: auto; + max-height: 580px; + aspect-ratio: 9 / 16; + width: auto; +} + +.preview-video { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* 标题由 ASS.js 渲染在 canvas 层,无需手动 overlay */ + +/* 滚动条 */ +.cover-config-panel::-webkit-scrollbar { + width: 6px; +} + +.cover-config-panel::-webkit-scrollbar-track { + background: transparent; +} + +.cover-config-panel::-webkit-scrollbar-thumb { + background: var(--border-light); + border-radius: var(--radius-sm); +} + +.cover-config-panel::-webkit-scrollbar-thumb:hover { + background: var(--text-tertiary); +} + +/* 封面标题文字层 — CSS 预览(替代 assjs) */ +.cover-title-overlay { + position: absolute; + left: 0; + width: 100%; + text-align: center; + font-family: 'DouyinSans', 'PingFang SC', 'Microsoft YaHei', sans-serif; + font-weight: bold; + line-height: 1.3; + pointer-events: none; + z-index: 10; + word-break: break-word; +} + +.cover-title-line { + white-space: nowrap; +} diff --git a/tauri-app/src/pages/VideoCreation/CoverDesign.tsx b/tauri-app/src/pages/VideoCreation/CoverDesign.tsx new file mode 100644 index 0000000..5a37c0c --- /dev/null +++ b/tauri-app/src/pages/VideoCreation/CoverDesign.tsx @@ -0,0 +1,418 @@ +/** + * 封面制作页面 (Step 5) + * ================= + * + * 生成短视频封面:背景图 + 标题文字压制 + * 背景来源:1. 视频首帧 2. Kling Omni-Image AI 生成 + * 标题位置:中上,允许换行 + */ + +import { useState, useRef, useEffect, useMemo } from 'react'; +import { invoke } from '@tauri-apps/api/core'; +import { useProjectStore } from '../../store'; +import { getCurrentProjectId } from '../../api/modules/localStorage'; +import { useLocalVideo } from '../../hooks/useLocalVideo'; +import { generateCoverAss, saveAssFile, htmlColorToAss, cssColorToAss } from '../../utils/assGenerator'; +import { useProgressStore } from '../../store/progressStore'; +import './CoverDesign.css'; +import '../../components/Slider/Slider.css'; + + + +interface CoverStyle { + fontSize: number; + color: string; + strokeColor: string; + strokeWidth: number; + shadow?: number; // 阴影偏移量(0=无阴影) + backgroundColor?: string; // 背景色(存在时用 BorderStyle: 3 色块,忽略描边和阴影) +} + +// 预设样式(4个字命名,按实际效果) +const STYLE_PRESETS = [ + { id: 'white', name: '白字暗底', color: '#FFFFFF', strokeColor: '#000000', strokeWidth: 0, backgroundColor: 'rgba(0,0,0,0.5)' }, + { id: 'black', name: '白字黑边', color: '#FFFFFF', strokeColor: '#000000', strokeWidth: 3 }, + { id: 'yellow', name: '黄字黑边', color: '#FFD700', strokeColor: '#000000', strokeWidth: 3 }, + { id: 'neon', name: '黑字黄底', color: '#000000', strokeColor: '#000000', strokeWidth: 0, backgroundColor: '#FFD700' }, +]; + +const VIDEO_HEIGHT = 1920; +const MARGIN_V = 360; +const MARGIN_LR = 108; + +/** + * 用 CSS text-shadow 多层偏移模拟外侧描边(不侵蚀文字内部,比 -webkit-text-stroke 更接近 libass) + */ +function generateTextShadow(strokeColor: string, strokeWidthPx: number, shadowPx: number): string | undefined { + if (strokeWidthPx <= 0 && shadowPx <= 0) return undefined; + const shadows: string[] = []; + const w = Math.max(0, Math.round(strokeWidthPx)); + for (let r = 1; r <= w; r++) { + shadows.push(`${-r}px ${-r}px 0 ${strokeColor}`); + shadows.push(`${r}px ${-r}px 0 ${strokeColor}`); + shadows.push(`${-r}px ${r}px 0 ${strokeColor}`); + shadows.push(`${r}px ${r}px 0 ${strokeColor}`); + shadows.push(`${-r}px 0 0 ${strokeColor}`); + shadows.push(`${r}px 0 0 ${strokeColor}`); + shadows.push(`0 ${-r}px 0 ${strokeColor}`); + shadows.push(`0 ${r}px 0 ${strokeColor}`); + } + if (shadowPx > 0) { + const s = Math.round(shadowPx); + shadows.push(`${s}px ${s}px ${s}px ${strokeColor}`); + } + return shadows.join(', '); +} + +/** + * 按最大字符数换行(中文场景),同时保留用户手动输入的换行 + */ +function wrapText(text: string, maxChars: number): string { + if (!text || maxChars <= 0) { + return text; + } + // 先按用户手动换行拆分成段落 + const paragraphs = text.split('\n'); + const wrappedParagraphs = paragraphs.map((paragraph) => { + const lines: string[] = []; + let current = ''; + for (const char of paragraph) { + if (current.length >= maxChars) { + lines.push(current); + current = char; + } else { + current += char; + } + } + if (current) { + lines.push(current); + } + return lines.join('\\N'); + }); + return wrappedParagraphs.join('\\N'); +} + +export default function CoverDesign() { + const segments = useProjectStore(state => state.segments); + const topic = useProjectStore(state => state.topic); + const coverConfig = useProjectStore(state => state.coverConfig); + const setCoverPath = useProjectStore(state => state.setCoverPath); + const setCoverConfig = useProjectStore(state => state.setCoverConfig); + const projectId = getCurrentProjectId(); + + const [caption, setCaption] = useState(topic || ''); + const [coverStyle, setCoverStyle] = useState({ + fontSize: 72, + color: '#FFFFFF', + strokeColor: '#000000', + strokeWidth: 2, + }); + const [selectedPreset, setSelectedPreset] = useState('white'); + + // 防抖后的样式值 — 用于预览渲染,避免滑块拖动时频繁更新 + const [debouncedCoverStyle, setDebouncedCoverStyle] = useState(coverStyle); + useEffect(() => { + const timer = setTimeout(() => setDebouncedCoverStyle(coverStyle), 150); + return () => clearTimeout(timer); + }, [coverStyle]); + + // 加载之前保存的封面配置 + useEffect(() => { + if (!coverConfig) return; + if (coverConfig.caption !== undefined) setCaption(coverConfig.caption); + if (coverConfig.coverStyle !== undefined) setCoverStyle(coverConfig.coverStyle); + if (coverConfig.selectedPreset !== undefined) setSelectedPreset(coverConfig.selectedPreset); + }, [coverConfig]); + + const videoRef = useRef(null); + const containerRef = useRef(null); + + // 预览缩放比例:与字幕模板一致,按视频预览容器高度 600px / 视频基准高度 1920px + // 字幕模板由 assjs 内部自动按视频高度缩放,这里手动对齐同一比例 + const previewScale = 600 / VIDEO_HEIGHT; + + // 固定取镜头1的视频路径作为首帧来源 + const shot1 = segments.find(s => Number(s.id) === 1); + const shot1VideoPath = shot1?.burnedVideoPath || shot1?.videoPath; + // 使用 useLocalVideo hook 加载本地视频(解决 asset:// 403 问题) + const { videoUrl: firstFrameVideoUrl } = useLocalVideo(shot1VideoPath); + + const applyPreset = (presetId: string) => { + const preset = STYLE_PRESETS.find(p => p.id === presetId); + if (!preset) { + return; + } + setSelectedPreset(presetId); + setCoverStyle({ + ...coverStyle, + color: preset.color, + strokeColor: preset.strokeColor, + strokeWidth: preset.strokeWidth, + shadow: (preset as any).shadow, + backgroundColor: (preset as any).backgroundColor, + }); + }; + + /** + * 获取背景图片本地路径(固定使用视频首帧) + */ + const getBackgroundImagePath = async (): Promise => { + if (!shot1VideoPath) { + throw new Error('镜头1没有可用视频,无法提取首帧'); + } + const outputResult = await invoke<{ code: number; data?: string; message: string }>('get_image_save_path', { + projectId, + filename: `cover_first_frame_${Date.now()}.jpg`, + }); + if (outputResult.code !== 200 || !outputResult.data) { + throw new Error(outputResult.message); + } + const extractResult = await invoke<{ code: number; data?: string; message: string }>('extract_video_first_frame', { + request: { + video_path: shot1VideoPath, + output_path: outputResult.data, + }, + }); + if (extractResult.code !== 200 || !extractResult.data) { + throw new Error(extractResult.message); + } + return extractResult.data; + }; + + const handleGenerate = async () => { + if (!projectId) { + return; + } + if (!caption.trim()) { + return; + } + + useProgressStore.getState().show('封面生成'); + + try { + // 1. 获取背景图片(视频首帧) + useProgressStore.getState().update('正在提取视频首帧...'); + const bgImagePath = await getBackgroundImagePath(); + + // 2. 生成 ASS 文件并压制标题字幕 + useProgressStore.getState().update('正在合成封面...'); + const maxCharsPerLine = Math.max(3, Math.floor(864 / coverStyle.fontSize)); + const wrappedText = wrapText(caption.trim(), maxCharsPerLine); + + const isBackgroundStyle = !!coverStyle.backgroundColor; + // BorderStyle: 3 的背景框大小由 outline 决定,需要 outline > 0 才能显示背景 + // outlineColor 和 backColor 设为相同颜色,让背景框和背景融合为统一色块 + const bgOutline = isBackgroundStyle ? 40 : coverStyle.strokeWidth; + const bgAssColor = isBackgroundStyle ? cssColorToAss(coverStyle.backgroundColor!) : '&H00000000'; + const assContent = generateCoverAss(wrappedText, { + fontSize: coverStyle.fontSize, + outline: bgOutline, + shadow: isBackgroundStyle ? 0 : (coverStyle.shadow || 0), + primaryColor: htmlColorToAss(coverStyle.color), + outlineColor: isBackgroundStyle ? bgAssColor : htmlColorToAss(coverStyle.strokeColor), + backColor: bgAssColor, + borderStyle: isBackgroundStyle ? 3 : 1, + alignment: 8, + marginV: MARGIN_V, + marginL: MARGIN_LR, + marginR: MARGIN_LR, + }); + + const assFilename = `cover_title_${Date.now()}.ass`; + const assPath = await saveAssFile(projectId, assFilename, assContent); + + // 确定输出路径(images 目录) + const outputResult = await invoke<{ code: number; data?: string; message: string }>('get_image_save_path', { + projectId, + filename: `cover_${Date.now()}.png`, + }); + if (outputResult.code !== 200 || !outputResult.data) { + throw new Error(outputResult.message); + } + const coverOutputPath = outputResult.data; + + // FFmpeg 压制封面 + const coverResult = await invoke<{ code: number; data?: string; message: string }>('generate_cover_image', { + request: { + image_path: bgImagePath, + ass_path: assPath, + output_path: coverOutputPath, + }, + }); + if (coverResult.code !== 200 || !coverResult.data) { + throw new Error(coverResult.message); + } + + // 3. 更新状态 + setCoverPath(coverOutputPath); + + // 4. 快照封面配置到 store(触发 saveMetaToLocalFile) + setCoverConfig({ + caption: caption.trim(), + coverStyle, + selectedPreset, + }); + + useProgressStore.getState().success('封面生成完成'); + } catch (error: any) { + const message = error?.message || String(error) || '封面生成失败'; + console.error('封面生成失败:', error); + useProgressStore.getState().error(message); + } + }; + + // ===== CSS 预览计算 ===== + const previewFontSize = debouncedCoverStyle.fontSize * previewScale; + const previewShadow = (debouncedCoverStyle.shadow || 0) * previewScale; + const previewMarginTop = MARGIN_V * previewScale; + const previewMarginH = MARGIN_LR * previewScale; + + const previewLines = useMemo(() => { + if (!caption.trim()) return []; + const maxCharsPerLine = Math.max(3, Math.floor(864 / debouncedCoverStyle.fontSize)); + const wrappedText = wrapText(caption.trim(), maxCharsPerLine); + return wrappedText.split('\\N'); + }, [caption, debouncedCoverStyle.fontSize]); + + const hasPreview = !!firstFrameVideoUrl && !!caption.trim() && previewLines.length > 0; + + return ( +
+ {/* 左侧:配置区域 */} +
+
+ {/* 标题文案输入 */} +
+ +

GP1ri@9sLfCB{qN)ozCWw*44{7-71U zVcBv{xT#u9$5SB`{xW%j0s*rR9U69#v@jd~`FHX4!5;5xuX^-W47F|s{^*lGyTcn@BqpMr39eFDmz-sAvv){=Jj;;X0gx3O)rcC7C5(F z!(H#!^P4b;8ki*$@cdxTC$nZbCC_)JnVT?Yh{u98WETD;Aa%fKUYv5Kx-G}3F%_l! zn!01CDKXd{od@79vLB`Qd~9i>PrDD(L4~HvqWu~>m_C&o+m)v14m&3=OqzWCUM6cq zuQ|i#)ccZYYyUrfCPOzYpGh}3Ck+*f>?!w3{50a`Er?Y~k$Ie$(cq$*1O__=t14Z`(d=K|R^UQWy{A z)*ir%X_~AqQUL(c?r}i7oGtkA#?6Xgc#Maysnhs#e_J6;wTp@^r2uEpcGG--zxTw4 zs7OIBDz+t8JIdzY&gor-!*+Nsh8r<8uUF<9afOD|_!LS`?)+HE`wKBgFx#*XG(aO- zECAa(T9Y)~6O!fbTc;?>WL+1*$!Yv0PX+7IPP)rx^W1cGS6f}ygp=B0$}@SUgO#l7 zGo#XT!xu0*mFWcyP%-8KpQR;t=3SAb13jZKCE;5&=X1y1(I0;{Xv%4hABjpiO!j;d z#g1@?UcNIb7|@UCb~n+^EOEx&PHYWEPqs{)y=q;t(rp`@E??=pTBe>qT?qemMivc$ z$mBjz_sW#9}>NrRW7>z#bi zU_b5mO^=x5RLBL!yu#OD23ka>O#uqSZDNg~rR%2($i-5JOjybol`ZHrvyR0AeCic| zg}WigB8Y%gkluIY*AF%9NfUP;+Jj0x_qX^xfCMq8TSA9vL?oV9F$as2fY;QHAXB>Ca4aP7{k4?i+n zpH!{ESdZO~4=X2i{^RFkXi0@2X6j}kc!9Jt(AyHs-Cob@_C=4Sh9cuS+FwtKZXw1b zXWWp%sbK#7t-ww9(=*9ns;p3v#0b3`@tX3Y8ogCLiJ2aG?`<5U0yFYN%bq{>uuE9O z`&j&nzwZ9D;)9NvXY)hnEQdFD#P-)ar{8q7O?}8}iMKlMdu3hmIA|{e0B}S@uh)mm z7t8-n?qRIcfCX6Tg4y{Ep>P{InJ~fx5zAxBfpBGi7X>sPTz~Ob`Zwv|x$7F!UQG)f zzrPT9EG-aWuy|lUpalKmYDtKSB1g!heJ#eV`bh-X4aIo&Kh|o|6e^?9>EzVzC$IQv z^(%vYsf3Z15>!Gyr8FkP6TTEgAU^`~MvNEGR&qK7JUC5`3Ei}5D^6>nwR;JZ?QTa# z)GI#ZsW-!k5B6CN@8b`0zW)ed?|UHmwR27W`t(Pk-j;ojlSdBwen-s!Tpj1oQ{@%>9Ld^!?@MOL!$$*1w1|6 zuD?jv7_@;1?iY3<8ha|c8Oxh5ZKbPH1+W6hElo_#m;s!Un6~sev0p*uoIVPpbgl^x zx0AGU*9tas;yXA@w0m3nw7I|a%mk-sYUv*QnN|ifH9J=&K8%uyTgbc z$gd9(LuOH@WLUaW7CQSMKWkyn$5E1cO;3iVRrh&%b1q6aZc0Dkb!Z>$RdmzPzp{ob zweMy8Q&8af^WxHFEQrtWv6K8dL_FKq34pd&h91Mv1g2jQOVWtl0x_r!VgKE9_JedS2?`lmJU$Eija(}-1u}4{u5HCIzpc0KOi$p@N=yRi5&?RZmi z$Xk%^Jyu90U{Vt_su=y13X_VOfn5c|aTZ@@e4SUb=-hgb_C0CqZKZ!4k@?Q(##h&X zIaSuUowkKIsbF$7u9MG0tbPHKO>1gtK8b)nNNEM;76J$6^@m?BR}}p8+nILz9%a5Z zdXAW7R6J-)Zd;&qk2mAYy0gE`g}YzNo;182MDt0w&rC!w#Gz*7@<4BInA_*P&i12d z$aQ9=mfP!z!;dqxB`G->Cf{qWrd?wNA(|9L& zzBc_QEK}&Fbk=3<7gTIUloWWZ6u!5O&~%j6f{(clBoj#v+$6-=YkMa&B@3t@Y7ZiZ z2~Y^Qf^P%B8vwQ54SVt+xM*k3Zr|NSX?t;_S;xlsGHwz12gWI!1qi~FR#XO74=l#r z?K*a?Dwl9Xk!rk{pviUc=$gyiQm1C%0zFV=jFzdXA7MSBhoUpRx6efD!fqp z+q1%gIduQZoV;oaa_FL$Vq7;%8prI?oM^kxGsSAIt+hz$q4I-wXUDpedRrcOy9lWb zTV@Y?{@v~S*0-wt;D7#>-(ZTl0G>4Le;*|aB9lK{K5>>c7)I&b8 zafo4LkIXSX|7Yr$Nd$Ew$W0bRQd)_>FMgu*;xhNV3!&H0C=K zV8e7>l=y^AcfOm z&$mNitg2w9!j#5{wYnP7ok8IH6}SX>_9z$mDM8+~e?M{qdD0@A)~ za25Qa!+kL<8^B`XNHGimj;c|g-w5MxxKG1 z(P=@pT2WIw#l6`!vPT1Kb zaq2t9W2Rz!yJGx}-GL{H(gB&AS~NVQg!Ro>EmwDXV(AtgwiRlyq~)qqEiG*lLc^+B zkO}!svgG?A&?S_-__WH7P3hlUYmK?-Td!OXxUJ`6U3YCx#&xri`uM+V%GeytZ|Eyq zcex;2R{Q7#xKD{ut?yLlqgWrE&=u;z@ihO9Pr3~2`kiMg8shp*71BZsaj4Yo3|_XG zKF;m;PG{|c<`Fwh>nj)`t3^u;Jv z>>_)i>9JnoastD_ypf0b)x?qQ#W#ZLU;2RvY35SfB}ob6`&So?Pens4luyubok;*&VgQxp$F=W5i|VP*FF z3EF$~C4Z#jmbyuI^=kVi~oTST-g!ll5{_bXWRtQ#0vQvl7=6@)lTbxdVzH>6)DxofRa_)47U@rf$STZ>t%PmCD z->+_emFv{hqeAJLfFqRV!9+s8+SNG#CTC9{D5@I*K{x@K3j+p_e!>$dBo$DLRb!7< z!of$0s(-+HVX2v~*(oo%!vl;P#_Vjw@KF{?!bCl0mv~ zu#ys!T9I3_gEAo$5v6Px7UmbG{0$Ebm4LBO_a+{2_o^zc0B^Zn4rWBp@et z)#>DY35iJHNF^v@!KUQo4>KG`KQ^8R*)LNyvVQ3Yzq^h)Tdh0iWCx9evx{;=ABv1B zy|7Gw1sR+3OA}?>oI7c80trYJO$Zc^mNwOovUS0Zt;_V=zxATCOXKpaOwqpF^sf@e z-X(2(RrPt;0=m+Avl{B5KviE=d91;|*c-T2U5kTidmDk#ufEGJ_&GDFVL^Lr{UVPBj6_5-P zMVn9rPUb{k86+x@5t|tVDm?;LTMBAo03RX@?8ij&Gx4R80cQdu5gLP=>&iPE+8Pjz z>=LY|A~G*4ElN``Z0etvwMF3 z7^NisnFjo5Db4E1%ZVXA%M5z)?nTGV#wf4CrN{RHaDc44i(g;X-gOHewP{v<@=&}1 z6%2Atqmqp^VIvF80vZ6|nyx8LM5XabFbxV6qd{a22L05%R;*jI3pxy3t$IvuKr-NP z8<7A~tSZ`4t`yx{DIrb$aj$eW8*sdB$yz2;Rc2VM@AL=u+@$skm3*LyMKoWhh6Kac zlPQyzYBuR#64p$MUsDf@`J>1+nACSfj1-MNDy(T()U`U7BdVVI>Mr@Ne~j5n$lh89$(B?X-}1`#c`#S2(H@}Lm*E4ERVRbO^v2@* zdPmk$dJmPetUBS8+}G|#*e&3Oe;CaWBH``X$*zn*HAJ7nIi1zXh!b&nWs zejYwLrJ-4P72o8nk<`)xiF4N?rkYkuhSIs*|3d-;ZkKXiYCl z$R3-VJ(}38hTV(1!HfNmpN255BSFmcO@^=`H5a$GgsHKf?7UaN?>gPL9 zc`b_xw)wF6m2nyam@0ecC}dJ;$aexOe8@PM8fR2tz_aosT*R%d zoQpkLZb45i^@Hl?2+lXp%v*yRQNH~3<7Ek5GKc&OH^A=-vzeg@y_-0={TTY8pF_(w zo`p;;ya+jwZeB+~c0qaVvtiMe(xdhtJTdL>AmpS$7R;g4Y)B|4cslz|g1GA_egFNB z3zmOe>$xUQlFvk+hR!xPms>i>@EA$abb1);`*&k=IMTJ=U25WX1IE?Hy)QZ%-(aRx zkCx(*-I`AP%TqLw%w4a86-KOL_xphr1}yk{p&Ee&tp+6kj@GT-6?#)DA@$3AZvTzrMThUP@4`cKGE-J-OD> zqoxNB%tYS@9qLQo>@NS0pWkF7ypnY0;&Yi#!=eAJrSi<(m`j)XmtG#^P zcFdy>bw zYCOg0z?TKNH=T#To=WhJ(3i@fthn3m&;{F~E=>DztM`?aH2sD)%@hy&ALi889VN30 z6FQ8x()6%{zsd9oI3{IuFfUN*>nK&>wLIb)syK%?iTK{>+3g*V6HMoN7HKec-zLbTXgWXYW{8+N*#96>6WZOnsz_M*`U~7K+qiEXq#!>4I z<#mRve=48ruk6fGiEL2j|lBqD>_4#!%voIlku3ugWHJ&EB@&P8~d z7vg_vr?HpQgA?p8#6N2Pb$_?}`MZjxhu3B9+iFK}{y^fi;6C}mFyaQ+ zt$~hVE{o9UjvR-WaB_0>(O5S``7{L`3rOob$ZJC~P%C9l^jh?uj6bh`8^;^!n0AUL zgA5M`8gs!C^mNJZk_S>tut#4!DlmOv%kS8vjQzV#X{{yOJEUPp8TX4$iIm^rDS@ zKaJ-*_inz#jdaL7iyxR2abYYcoxh%(d0}%TQ@ax-)+*+wb-(`CNClrGYhPt;1S7u(Z$Eqi>zHe{fG+WY@eLKe15Q5U!fKD@m@~SHfK9=qNK|SUj#?>PtI1 zhDh;{J=;FS?NLhN-3oDR&ZLoxDmsejRQxc0JQ7(H;=s)iX+b?|pQ|Sz=v3sB+pSJu z6#%IMlBp$raTOC%u+(5OL?{Cm3?u^>Q0mJ_shvmC0FWOMF@$U&Eucgm41h|LC5Nnw zuU(h5BXoj99tYaSdafP6va+2TQ{L1EumBfRrkri4Kbb*+Z!s@3Yx6 zpowHDy8Q+qT?CLDPKQr92mZ&;1%WqG5S8(QKpCQ1mL|UefC8vZfGgN0ikeP)8n~!d zC_yws$Ps;CXg6;<8bDP6Ah4JMi#8BsU;`Mr1|hq(dCW$G%fJ9Yn+^!QpWbfE0HpGz zC`%v4SAB{HU|!w{@(#I!UJ?$V-w(PTV$?Y=AKPND38Dso1%%|~KF%+=epy`?3v;a) zl)1451*qsg)VAH1SXrCUC)mPx4KY!On2{Xdt51J;8G|F4#_as%V{lKU%42gAW4uXl z5RqE5BG#t)YDRYN$*i6P%4jE6&6LiH&h3Mh?5sQwZQ+=CD*^;?S20kGty|jM-}_ZZ zP(f=rVpT)qmGdP7<09$Tq$9!GeGC}~-W;_U=20pb2Pefws1nGQtfik58Ld3x8g zwm^Df_beH!R+1VcJ?E%)JpG*=0-1=e#BGMX#c5*#qIaUJLD%8@U{Y)@^jl!T#CdG?%o+~Rbup#)Z-TAB%z-}!!}+O>I9yS89bwRj#lW##b4qD$ILQeSCJUzW&$jp*@pGog`>{Z>fHk)9*j z_3jV39cHMCjo=0Vdjr@ggro!l=ul8W;fno$g*&VeCtYwLYhEMMJA_@}p?d;l9x2{cx#cPkT_*Assj+DCF~?Xj z2oU3oW|fHskk_lo{Kro@8BCH^;b{&5%sk{wPD+}cA#t!U&1G3z7@%a_7!Qa>?r~R2 zE85!Hv9Q07S*=OI>%u|L8?gpQL<7dS_$sxpm|!_c`v&IAti(qyrMGn!C=G`@#JN^3 zogc_>#&5r65&N|A)c7B&#)(eJ!0(Q@bKKMTg%PG9Lig%l3^F^VP(eh2-mcDYkZcZk z|Njm&N>Tlsste%Zo63*G{svf3c_V@0^OZqll+?f?V^#v#Aq@=B1%dojiA12&8#1SG zK5f`_vYS9j+#PyKCKcwpR@k$IA0y>Chey`Q^VM{+umY9^J99n3r+8FLmF>^%?>xAw z{3NA=IYy7vu&q}UyI^Hv^q?hrxjMxy0vjww^DJL5XuF;zV^V*)FC+eo5ie6s(&vBt zWr_O1MHa2Jl1T=cig9K;rD&@CnW^$V;eY9gG>wHV&tZd^*rw!&-C_=P!t`BJ#cq--KJR_R-vAJ1*)@bs=;iDWl~CPB`3^CD6$(ge&w3}UC{HZZfFwT~C02S7lZ zpusy3O(yFzsuBcRU7k9Uf)14xs=&g%&8MRtQ-3LDW14*Qn)PTSzij4o;&t>Jk&x#O zwiKq?x=l16ft<{^K6)4ZSHjBP7*eprJ9W&6B*;tU=d1yHyYrk@%t*t)0IRUvS@N)IoXie{TO6CeVtV`BnBZ$P8lCSQCvYErkfJJDlc&>b;a(y z`s_b`X2|ls>Rj#bhz~Ws+@pS7;>P*s{iUw0R^_nNHUC?#tG^b5Ll5zjJ%Ot93upW- z6cAiVI=&`bj(Hlht_{%EzGEp18_Ev?#KgI8u*kQXQz*DzBXHxkV&Y~6{AseWfov=S z*;T_rlQv+E5HLNij`Ops@!fei0i-=pI>!i_v+b!2(9Cx9O4R7xk~RQW5AoBGj5F>F zjsHtlK_;~^7!|xK4{~@NAADg#Z33+^A($c^hO5XK1!(pe;UFd;J%$x88gG%^Ld<5R zC){7K-TN8}r+_$ol1g%3ERA#=mU;C4pycmqR?!ZAF4K%Dj#_^)O^YlAyQ$@60} zRQk`#r=V=*qW#9>ilxGsmnBDmtbMqHZMR%96vVQ27n1hr&?h5Kza2k+0`K?TnKe1Q$&lm`8T zhDl>d%1dB9eoX|G&md#D!30P>TM*5Z!A9=H>=3HFyW(Bm?wb?FSLZegVtAe~0oM{| z%lMvUXmj12gM&vYI$tFeYBP_?$Vnsm(YakC=hl%l!#Au_kT&6bShwYC7d=S;2{#r z?<7ngqXX%*pH14cPDav)6)B;+B_I}9fQkf!O|ti%q|K2OKR+~FfFE3G#HS2%C%gJj zx7d~3be)8L0|I@pSIZ~yF$-}p|^8}L=n@t&tQIDLf zsgOBk81t$EjC8wz_8_A*pN@#OwYFz+(J&u47k%cwov8|ujCv%Urs}WiH$`Nv{;OU+ z(AX~bq+0d*Pouo$$@8J>o^NZv*SL8m?ELjsQ7B|rr3n#|Ywjp-!Mn9K$GTUdo6V(B z9unXFNfvP2KC#Abe3xkRmRK;CfG1&Ro=N=n#~SEiedR)*btD@Tlo>Px6Go{{KwBZSxLi{FMC&l=%%(k{FZBG$t-&E1uSCdErXf??pnmK5kLH3C#-J zmTxs4s4TE)lCv?=>`JUTf%+%KyxqQAp_x-b&ZxkUYROklH!F6ZAbc8}!l!~Fqqj~Y z81T_kmNNAlv#KCq5JkQ)KqnSU2?R`M%ZWkdMW%Kz?-bC~BDRvtf@oa2VZh5y>*9CQ zo3pR5{NQFzPX>->R*=i!^Zt_{sgNc2^^w$Ft|+6Sj(?3V^*78s>vO9HUsO{}@c1KA z4cIt@_%LeLg7xa;jq0D)4N~2iXM{3_Gv}_z3xM10fI>5o2xKTh+<+7Dit-Lh2;q8-K;*0Mjgvz?M|_T(ve@as6B zBE-LUNa00Dym$`5^OGR=1i>-(LGIqjnkhWG>-}zLUqRmr-m@Ieo8Hm%I#N}rtq7EE+Std?hI(&AOtY*ad~A^irq*6un-H^hMB6l1(B zwzT8a;}kv9TbUXL-4;hVf5bbU8%>+69$NU98-6mYvzlaS5M}!|YH7%gk;eOyD(T}Q zC9hM`A@(Su_n4#i;7LQoE0K^SH;FcS#26r;Lj~aD9&h4-PO?BCy)+O}ZZ$#xD3a`I zTX31`WupBdw3&Qh++fQUIVqj)XC;i~iBP+Rnb8xy+kCry^>w{Oz7TBvt9FY; zy;tA=&UIC8D@IwF@B3suud(gomdt(WpZZ4|0ML$KGY*f#=$g~WT&NAIj7&TCBkT!K zImsfTNg$)BWDH?YyG`qhKR`kL3nc57F9z{dTZU zg}{VuEuG1h%6gi_TVlJxBnE$f_xeb)&$kaf|3(a>fZ94|n?gncE$!ak62ni%3nQCe z&hMy!vPg^tQHl*`QsOT#J3S0N!grWtDQ;Vt;FS8^QqNM}s?6a>r2VTd6F_|aEy6cD zpYliF#wwGj{KJL-qbibh%`duKl=I$rBb|BggTr`zn<|_Nhqaa`K6@OA4zwnNswq|Au^H}3W-iKa;9PjTa`f& zLDOL?PBQQ6uE}8Tt+U|^WiPXjGI#{>HsprRTDDB?0H}Xxs`iCtUt)1r09P0#j0(GL zlBR1K%Sqc-w5^wdjN;ePisUWO99;wxblN%%ha)llSTMbrX~^{EPuVb<;x-k7uQZ#u zSh<37i45XrhW=kC0yt;yfB|j9M=w;0ZXv%~bX3Y_s0zvbh&I3!)_<7AFEm(68we3@ zL6!Z-&o8oKdO?i0(=0KRw4=Os6@=b1KYeXJrd0Rsx_ENr!tGD_eZQ05{QGzN!c_b( z-s{eHZ)=)P)JB2V(Wj%DI)77zFzd{#m`0x1q^qA2f@2cr5P^CarmUQSpn~@xDRiZo z;~6-0mq7^}Y((`@evpKk4kti8J>c5(hO4kscD7@&3Y#>=PlI$L6oiV(zkKdq!(&=s z?LxKUVivek=0;@23yrshT5tPlICNAv`QeCDmU$}Wlzo;mOH|$4 z6|(D^PWtzB?i*G47GcJ$(>LEJIaPNM_DY*`(_6XkNOGw6#F*6|z$Ail9r&vF?F;-H zko;{%4DG1$QE0LQSvsGZQ<(4MyY=-W7%Wv^PekLqJgfQD>Dh@$dY)c_vxmDIqDs=g zv{s0E`kG9PG1GURQClaEkoW6LUlONKw*2R&_XFa~rZjv;RqoF3Cfrw$_E{E3UB3t( z956J}Dp48XrIOPi@Z>U5*c0VDDYoVT+GSNxtlX0c#1{|gaayTsv{!Er+UfAiDVwJ?JFP**F>s$34nbP+~e=Qj&PZ?EVyDBf0HI^))w1`+DnV_I+ z2hM*Zw!dFJx4j>f-Zon`{~+Dvu6XgYJx6g7|A#EB^~i3LC$gW$`*R$X#0uxW%UM&o zt>iaxDjt0#4SvA1gSE@V&RnF=U<`!H!D$%L*LRz?n?6K;^#p5WkBKs+8tt8VFpJHx z+|7v@7wbT>IkrTYW7^@WHFdLXvqv@RWwp#6mn{dcU7y3j)DnOYdnf7u%fp;sk8_<4 zgWi|*Eauh-;(I?m2nosINV&ttqn#wmU*XszaQAdg84RG7o4yhFdMVzM@Mcuk1U{fl zrJ}0|cC!a(@+qKOMIYxOY0E{&(g*JW2vn>%u<&$#j;_TF! zq-#C(u`vGz$7j8n7P+xlxfy%6=T(o`@k#G3M<;U@yg$`kFU!rr7&?~bs>v+IfjaUL z!Xjcg_ykOmvw_p5ILf#o_f6SrR9i2GZXLk~yQA#T@_NPKPP7J5S?MN@L}jh1fpphv zdE!N+P0Lc4X&oIG3jM0wVuE#R<43b8>8h=KAB#+6b(RI2l?1HfcOi+pcZ}S%(Lx?N zV!Gq}bi)7DpNnt=lc0cJ**ejlv@_K!fYs=(s#(_8L})tK1)ENAy2b=Cf6-_Q7(>2$ zD6g)&$oZr!quIxF{MvJ?+)mh2egI3d5T`=@mp8y|NVJ-xltKhSZHNmLPjg2SNHwj> z%+;h3gSVA~%{0*?a3=C*h2ga|#2u~5NXIsAaUOOe8oP6k1 zsm;jEi5kD@o)m8q08&vm^8BZj)}Iy$lxJj2q)WNRFc@tVPc|uvp4EwOP|*cH`YIOP z`9#G`sN$mZy65)q-*5Z8c{Mo@rezgyj5aGX1S%-WZ|I;JnPHhhbB(EKN{V^b)UuzL zft-LtTU{9q+lJAT=t2HB)F$wrwSom(FR`CT{FAGCBa~|t4@Xhf|Bxa_Tel*h1YQwwK5F|TH*z1St`AX?ZvgjY7-f* zf@sxLPFlklliWzJ7M(qz09{)pv%m?+ZE;LJi%R0(z`6sCzJ;uux7WRUFovl z1xtSU?Qfsx{9SWu?%9q;v%Z8b?@bM(g%5q>M0k)FhwW@T-K;=6FbOuVVbLp8h6%# zOw{k|?&?l^$J$!)Mh_%RX+#@k(V>3(+rijqtfoI|eRb1YgqV;YNX$r#uy9zQP!TlQ zz9%)eTB!0woQI3=;ywtaelm7R$ZXwfn{b@_c<5F!E7>`Ev9B}K(GvIsFen`|xQkk&@1mV(e{X$Trr<%P=$}&$ zZ>xXbtyjL6SA?|v+EcY!YCZnl^)bT7#me7B)br5?422s#>1lPMq@SwAL``-{FgY(*r~>7A>iJ=lV$0B=tr5h;7bm_q%Fu3o9|Lb%rl(8(B+!2yWO5 zsC;7Y9E_xGf+%x}k^j!k)m_qWf#PEhIp=EiGu*oak0O&Q>)-69%QU+wYy`;PyfX2t zRKKl>4Z*sZYp2}0C4q0T{?(u^8=HfC#O9jaBI|tD44FBM%@Mwv)Hxy%?^c3z!>|ch zn3y)}GFs{V$InkPq(3u@zC%hpg7hJwvNAd_0)te4dGF$(UXG7xVV&%@VWLKdyzucY z5t)Cckr}F)k#7ri>gHV2P{roIaYg zvSL?^{v6pkicXgeoDTiy_v;H*)f7M7YV@*V*@U-A-{t$Z-MMooOTujW`1Rae69B^2iE#h` literal 0 HcmV?d00001 diff --git a/tauri-app/public/audio/presets/chat1_female_new-3.mp3 b/tauri-app/public/audio/presets/chat1_female_new-3.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..6602a1a013f95d388e47b8a744e16abe036d7a21 GIT binary patch literal 62228 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 =>FaVbe^ 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 D=wn>w literal 0 HcmV?d00001 diff --git a/tauri-app/public/audio/presets/chengshu_jiejie.mp3 b/tauri-app/public/audio/presets/chengshu_jiejie.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..1949e2499c808136217769e86cddb95d3713714c GIT binary patch literal 57620 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