Showing preview only (6,792K chars total). Download the full file or copy to clipboard to get everything.
Repository: saturndec/waoowaoo
Branch: main
Commit: 9aff44e37a2d
Files: 1330
Total size: 6.2 MB
Directory structure:
gitextract_4hr0t8jh/
├── .dockerignore
├── .eslintrc.json
├── .gitignore
├── .husky/
│ ├── pre-commit
│ └── pre-push
├── .nvmrc
├── .tmp_check_task.ts
├── CHANGELOG.md
├── Dockerfile
├── README.md
├── README_en.md
├── caddyfile
├── debug-request.json
├── docker-compose.test.yml
├── docker-compose.yml
├── eslint.config.mjs
├── extract_chinese.py
├── lib/
│ └── prompts/
│ ├── character-reference/
│ │ ├── character_image_to_description.en.txt
│ │ ├── character_image_to_description.zh.txt
│ │ ├── character_reference_to_sheet.en.txt
│ │ └── character_reference_to_sheet.zh.txt
│ ├── novel-promotion/
│ │ ├── agent_acting_direction.en.txt
│ │ ├── agent_acting_direction.zh.txt
│ │ ├── agent_character_profile.en.txt
│ │ ├── agent_character_profile.zh.txt
│ │ ├── agent_character_visual.en.txt
│ │ ├── agent_character_visual.zh.txt
│ │ ├── agent_cinematographer.en.txt
│ │ ├── agent_cinematographer.zh.txt
│ │ ├── agent_clip.en.txt
│ │ ├── agent_clip.zh.txt
│ │ ├── agent_shot_variant_analysis.en.txt
│ │ ├── agent_shot_variant_analysis.zh.txt
│ │ ├── agent_shot_variant_generate.en.txt
│ │ ├── agent_shot_variant_generate.zh.txt
│ │ ├── agent_storyboard_detail.en.txt
│ │ ├── agent_storyboard_detail.zh.txt
│ │ ├── agent_storyboard_insert.en.txt
│ │ ├── agent_storyboard_insert.zh.txt
│ │ ├── agent_storyboard_plan.en.txt
│ │ ├── agent_storyboard_plan.zh.txt
│ │ ├── character_create.en.txt
│ │ ├── character_create.zh.txt
│ │ ├── character_description_update.en.txt
│ │ ├── character_description_update.zh.txt
│ │ ├── character_modify.en.txt
│ │ ├── character_modify.zh.txt
│ │ ├── character_regenerate.en.txt
│ │ ├── character_regenerate.zh.txt
│ │ ├── episode_split.en.txt
│ │ ├── episode_split.zh.txt
│ │ ├── image_prompt_modify.en.txt
│ │ ├── image_prompt_modify.zh.txt
│ │ ├── location_create.en.txt
│ │ ├── location_create.zh.txt
│ │ ├── location_description_update.en.txt
│ │ ├── location_description_update.zh.txt
│ │ ├── location_modify.en.txt
│ │ ├── location_modify.zh.txt
│ │ ├── location_regenerate.en.txt
│ │ ├── location_regenerate.zh.txt
│ │ ├── screenplay_conversion.en.txt
│ │ ├── screenplay_conversion.zh.txt
│ │ ├── select_location.en.txt
│ │ ├── select_location.zh.txt
│ │ ├── single_panel_image.en.txt
│ │ ├── single_panel_image.zh.txt
│ │ ├── storyboard_edit.en.txt
│ │ ├── storyboard_edit.zh.txt
│ │ ├── voice_analysis.en.txt
│ │ └── voice_analysis.zh.txt
│ ├── proxy.ts
│ └── skills/
│ ├── api-config-template.system.txt
│ └── tutorial.system.txt
├── messages/
│ ├── en/
│ │ ├── actions.json
│ │ ├── apiConfig.json
│ │ ├── apiTypes.json
│ │ ├── assetHub.json
│ │ ├── assetLibrary.json
│ │ ├── assetModal.json
│ │ ├── assetPicker.json
│ │ ├── assets.json
│ │ ├── auth.json
│ │ ├── billing.json
│ │ ├── common.json
│ │ ├── configModal.json
│ │ ├── errors.json
│ │ ├── landing.json
│ │ ├── layout.json
│ │ ├── modelSection.json
│ │ ├── nav.json
│ │ ├── novel-promotion.json
│ │ ├── profile.json
│ │ ├── progress.json
│ │ ├── providerSection.json
│ │ ├── scriptView.json
│ │ ├── smartImport.json
│ │ ├── stages.json
│ │ ├── storyboard.json
│ │ ├── video.json
│ │ ├── voice.json
│ │ ├── workspace.json
│ │ ├── workspaceDetail.json
│ │ └── worldContextModal.json
│ └── zh/
│ ├── actions.json
│ ├── apiConfig.json
│ ├── apiTypes.json
│ ├── assetHub.json
│ ├── assetLibrary.json
│ ├── assetModal.json
│ ├── assetPicker.json
│ ├── assets.json
│ ├── auth.json
│ ├── billing.json
│ ├── common.json
│ ├── configModal.json
│ ├── errors.json
│ ├── landing.json
│ ├── layout.json
│ ├── modelSection.json
│ ├── nav.json
│ ├── novel-promotion.json
│ ├── profile.json
│ ├── progress.json
│ ├── providerSection.json
│ ├── scriptView.json
│ ├── smartImport.json
│ ├── stages.json
│ ├── storyboard.json
│ ├── video.json
│ ├── voice.json
│ ├── workspace.json
│ ├── workspaceDetail.json
│ └── worldContextModal.json
├── middleware.ts
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── prisma/
│ ├── schema.prisma
│ └── schema.sqlit.prisma
├── scripts/
│ ├── billing-cleanup-pending-freezes.ts
│ ├── billing-reconcile-ledger.ts
│ ├── bull-board.ts
│ ├── check-api-handler.ts
│ ├── check-capability-catalog.mjs
│ ├── check-image-urls-contract.ts
│ ├── check-log-semantic.ts
│ ├── check-media-normalization.ts
│ ├── check-model-config-contract.mjs
│ ├── check-no-console.ts
│ ├── check-outbound-image-runtime-sample.ts
│ ├── check-outbound-image-success-rate.ts
│ ├── check-outbound-image-unification.ts
│ ├── check-pricing-catalog.mjs
│ ├── cleanup-remove-legacy-voice-data.ts
│ ├── diagnose-project.ts
│ ├── guards/
│ │ ├── api-route-contract-guard.mjs
│ │ ├── changed-file-test-impact-guard.mjs
│ │ ├── file-line-count-guard.mjs
│ │ ├── image-reference-normalization-guard.mjs
│ │ ├── locale-navigation-guard.mjs
│ │ ├── no-api-direct-llm-call.mjs
│ │ ├── no-duplicate-endpoint-entry.mjs
│ │ ├── no-hardcoded-model-capabilities.mjs
│ │ ├── no-internal-task-sync-fallback.mjs
│ │ ├── no-media-provider-bypass.mjs
│ │ ├── no-model-key-downgrade.mjs
│ │ ├── no-multiple-sources-of-truth.mjs
│ │ ├── no-provider-guessing.mjs
│ │ ├── no-server-mirror-state.mjs
│ │ ├── prompt-ab-regression.mjs
│ │ ├── prompt-i18n-guard.mjs
│ │ ├── prompt-json-canary-guard.mjs
│ │ ├── prompt-semantic-regression.mjs
│ │ ├── task-loading-baseline.json
│ │ ├── task-loading-guard.mjs
│ │ ├── task-state-unification-guard.sh
│ │ ├── task-status-cutover-audit.sh
│ │ ├── task-submit-compensation-guard.mjs
│ │ ├── task-target-states-no-polling-guard.mjs
│ │ ├── test-behavior-quality-guard.mjs
│ │ ├── test-behavior-route-coverage-guard.mjs
│ │ ├── test-behavior-tasktype-coverage-guard.mjs
│ │ ├── test-route-coverage-guard.mjs
│ │ └── test-tasktype-coverage-guard.mjs
│ ├── media-archive-legacy-refs.ts
│ ├── media-backfill-refs.ts
│ ├── media-build-unreferenced-index.ts
│ ├── media-mapping.ts
│ ├── media-restore-dry-run.ts
│ ├── media-safety-backup.ts
│ ├── migrate-cancelled-to-failed.ts
│ ├── migrate-image-urls-contract.ts
│ ├── migrate-local-to-minio.ts
│ ├── migrate-to-minio.sh
│ ├── migrate-to-minio.ts
│ ├── migrations/
│ │ ├── migrate-capability-selections.ts
│ │ ├── migrate-custom-pricing-v2.ts
│ │ ├── migrate-gateway-route-openai-compat.ts
│ │ ├── migrate-graph-artifacts-unique-index.ts
│ │ ├── migrate-model-config-contract.ts
│ │ ├── migrate-qwen-to-bailian.ts
│ │ ├── migrate-release-blockers.ts
│ │ └── reports/
│ │ ├── model-config-migration-report.apply.json
│ │ ├── model-config-migration-report.post-alias-apply.json
│ │ ├── model-config-migration-report.post-alias-dryrun.json
│ │ └── model-config-migration-report.pre-apply.json
│ ├── task-error-stats.ts
│ ├── test-full-image-flow.ts
│ ├── test-image-url-flow.ts
│ ├── test-minio.ts
│ ├── test-regression-runner.sh
│ ├── test-sign-api.ts
│ ├── tmp-cleanup-project-models.mjs
│ ├── tmp-find-old-model.mjs
│ └── watchdog.ts
├── src/
│ ├── app/
│ │ ├── [locale]/
│ │ │ ├── auth/
│ │ │ │ ├── signin/
│ │ │ │ │ └── page.tsx
│ │ │ │ └── signup/
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── page.tsx
│ │ │ ├── profile/
│ │ │ │ ├── components/
│ │ │ │ │ ├── ApiConfigTab.tsx
│ │ │ │ │ ├── api-config/
│ │ │ │ │ │ ├── DefaultModelSection.tsx
│ │ │ │ │ │ ├── ProviderCard.tsx
│ │ │ │ │ │ ├── ProviderSection.tsx
│ │ │ │ │ │ ├── hooks.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── provider-card/
│ │ │ │ │ │ │ ├── ModelTemplateAssistantModal.tsx
│ │ │ │ │ │ │ ├── ProviderAdvancedFields.tsx
│ │ │ │ │ │ │ ├── ProviderBaseFields.tsx
│ │ │ │ │ │ │ ├── ProviderCardShell.tsx
│ │ │ │ │ │ │ ├── hooks/
│ │ │ │ │ │ │ │ └── useProviderCardState.ts
│ │ │ │ │ │ │ └── types.ts
│ │ │ │ │ │ └── types.ts
│ │ │ │ │ └── api-config-tab/
│ │ │ │ │ ├── ApiConfigProviderList.tsx
│ │ │ │ │ ├── ApiConfigTabContainer.tsx
│ │ │ │ │ ├── ApiConfigToolbar.tsx
│ │ │ │ │ ├── DefaultModelCards.tsx
│ │ │ │ │ └── hooks/
│ │ │ │ │ └── useApiConfigFilters.ts
│ │ │ │ └── page.tsx
│ │ │ ├── providers.tsx
│ │ │ └── workspace/
│ │ │ ├── [projectId]/
│ │ │ │ ├── components/
│ │ │ │ │ └── Sidebar.tsx
│ │ │ │ ├── episode-selection.ts
│ │ │ │ ├── hooks/
│ │ │ │ │ └── useProject.ts
│ │ │ │ ├── modes/
│ │ │ │ │ └── novel-promotion/
│ │ │ │ │ ├── NovelPromotionWorkspace.tsx
│ │ │ │ │ ├── StageNavigation.tsx
│ │ │ │ │ ├── WorkspaceProvider.tsx
│ │ │ │ │ ├── WorkspaceStageRuntimeContext.tsx
│ │ │ │ │ ├── components/
│ │ │ │ │ │ ├── AssetLibrary.tsx
│ │ │ │ │ │ ├── AssetsStage.tsx
│ │ │ │ │ │ ├── ConfigStage.tsx
│ │ │ │ │ │ ├── NovelInputStage.tsx
│ │ │ │ │ │ ├── PanelEditForm.tsx
│ │ │ │ │ │ ├── PromptsStage.tsx
│ │ │ │ │ │ ├── ScriptStage.tsx
│ │ │ │ │ │ ├── ScriptView.tsx
│ │ │ │ │ │ ├── SmartImportWizard.tsx
│ │ │ │ │ │ ├── StoryboardStage.tsx
│ │ │ │ │ │ ├── VideoStage.tsx
│ │ │ │ │ │ ├── VideoStageRoute.tsx
│ │ │ │ │ │ ├── VoiceStage.tsx
│ │ │ │ │ │ ├── VoiceStageRoute.tsx
│ │ │ │ │ │ ├── WorkspaceAssetLibraryModal.tsx
│ │ │ │ │ │ ├── WorkspaceHeaderShell.tsx
│ │ │ │ │ │ ├── WorkspaceRunStreamConsoles.tsx
│ │ │ │ │ │ ├── WorkspaceStageContent.tsx
│ │ │ │ │ │ ├── WorkspaceTopActions.tsx
│ │ │ │ │ │ ├── assets/
│ │ │ │ │ │ │ ├── AddLocationModal.tsx
│ │ │ │ │ │ │ ├── AssetToolbar.tsx
│ │ │ │ │ │ │ ├── AssetsStageModals.tsx
│ │ │ │ │ │ │ ├── AssetsStageStatusOverlays.tsx
│ │ │ │ │ │ │ ├── CharacterCard.tsx
│ │ │ │ │ │ │ ├── CharacterEditModal.tsx
│ │ │ │ │ │ │ ├── CharacterProfileCard.tsx
│ │ │ │ │ │ │ ├── CharacterProfileDialog.tsx
│ │ │ │ │ │ │ ├── CharacterSection.tsx
│ │ │ │ │ │ │ ├── ImageEditModal.tsx
│ │ │ │ │ │ │ ├── LocationCard.tsx
│ │ │ │ │ │ │ ├── LocationEditModal.tsx
│ │ │ │ │ │ │ ├── LocationSection.tsx
│ │ │ │ │ │ │ ├── UnconfirmedProfilesSection.tsx
│ │ │ │ │ │ │ ├── VoiceSettings.tsx
│ │ │ │ │ │ │ ├── character-card/
│ │ │ │ │ │ │ │ ├── CharacterCardActions.tsx
│ │ │ │ │ │ │ │ ├── CharacterCardGallery.tsx
│ │ │ │ │ │ │ │ └── CharacterCardHeader.tsx
│ │ │ │ │ │ │ ├── hooks/
│ │ │ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ │ │ ├── useAssetModals.ts
│ │ │ │ │ │ │ │ ├── useAssetsCopyFromHub.ts
│ │ │ │ │ │ │ │ ├── useAssetsGlobalActions.ts
│ │ │ │ │ │ │ │ ├── useAssetsImageEdit.ts
│ │ │ │ │ │ │ │ ├── useBatchGeneration.helpers.ts
│ │ │ │ │ │ │ │ ├── useBatchGeneration.ts
│ │ │ │ │ │ │ │ ├── useCharacterActions.ts
│ │ │ │ │ │ │ │ ├── useLocationActions.ts
│ │ │ │ │ │ │ │ ├── useProfileManagement.ts
│ │ │ │ │ │ │ │ └── useTTSGeneration.ts
│ │ │ │ │ │ │ └── location-card/
│ │ │ │ │ │ │ ├── LocationCardActions.tsx
│ │ │ │ │ │ │ ├── LocationCardHeader.tsx
│ │ │ │ │ │ │ └── LocationImageList.tsx
│ │ │ │ │ │ ├── prompts-stage/
│ │ │ │ │ │ │ ├── PromptEditorPanel.tsx
│ │ │ │ │ │ │ ├── PromptListCardView.tsx
│ │ │ │ │ │ │ ├── PromptListPanel.tsx
│ │ │ │ │ │ │ ├── PromptListTableView.tsx
│ │ │ │ │ │ │ ├── PromptsStageLayout.tsx
│ │ │ │ │ │ │ ├── PromptsStageShell.tsx
│ │ │ │ │ │ │ ├── hooks/
│ │ │ │ │ │ │ │ └── usePromptStageActions.tsx
│ │ │ │ │ │ │ └── runtime/
│ │ │ │ │ │ │ ├── hooks/
│ │ │ │ │ │ │ │ ├── usePromptAiModifyFlow.ts
│ │ │ │ │ │ │ │ ├── usePromptAppendFlow.ts
│ │ │ │ │ │ │ │ ├── usePromptAssetMention.ts
│ │ │ │ │ │ │ │ ├── usePromptDraftByShot.ts
│ │ │ │ │ │ │ │ └── usePromptEditorRuntime.ts
│ │ │ │ │ │ │ ├── promptStageRuntime.types.ts
│ │ │ │ │ │ │ ├── promptStageRuntime.utils.ts
│ │ │ │ │ │ │ └── promptStageRuntimeCore.tsx
│ │ │ │ │ │ ├── script-view/
│ │ │ │ │ │ │ ├── ScriptViewAssetsPanel.tsx
│ │ │ │ │ │ │ ├── ScriptViewContainer.tsx
│ │ │ │ │ │ │ ├── ScriptViewCore.tsx
│ │ │ │ │ │ │ ├── ScriptViewRuntime.tsx
│ │ │ │ │ │ │ ├── ScriptViewScriptPanel.tsx
│ │ │ │ │ │ │ ├── SpotlightCards.tsx
│ │ │ │ │ │ │ ├── asset-state-utils.ts
│ │ │ │ │ │ │ └── clip-asset-utils.ts
│ │ │ │ │ │ ├── smart-import/
│ │ │ │ │ │ │ ├── hooks/
│ │ │ │ │ │ │ │ └── useWizardState.ts
│ │ │ │ │ │ │ ├── steps/
│ │ │ │ │ │ │ │ ├── StepConfirm.tsx
│ │ │ │ │ │ │ │ ├── StepMapping.tsx
│ │ │ │ │ │ │ │ ├── StepParse.tsx
│ │ │ │ │ │ │ │ └── StepSource.tsx
│ │ │ │ │ │ │ └── types.ts
│ │ │ │ │ │ ├── storyboard/
│ │ │ │ │ │ │ ├── AIDataModal.tsx
│ │ │ │ │ │ │ ├── AIDataModal.types.ts
│ │ │ │ │ │ │ ├── AIDataModalFormPane.tsx
│ │ │ │ │ │ │ ├── AIDataModalPreviewPane.tsx
│ │ │ │ │ │ │ ├── CandidateSelector.tsx
│ │ │ │ │ │ │ ├── ImageEditModal.tsx
│ │ │ │ │ │ │ ├── ImageEditModalAssetPicker.tsx
│ │ │ │ │ │ │ ├── ImageEditModalSelectedAssets.tsx
│ │ │ │ │ │ │ ├── ImageSection.css
│ │ │ │ │ │ │ ├── ImageSection.tsx
│ │ │ │ │ │ │ ├── ImageSectionActionButtons.tsx
│ │ │ │ │ │ │ ├── ImageSectionCandidateMode.tsx
│ │ │ │ │ │ │ ├── InsertPanelButton.tsx
│ │ │ │ │ │ │ ├── InsertPanelModal.tsx
│ │ │ │ │ │ │ ├── PanelActionButtons.tsx
│ │ │ │ │ │ │ ├── PanelCard.tsx
│ │ │ │ │ │ │ ├── PanelVariantModal.tsx
│ │ │ │ │ │ │ ├── PanelVariantModal.types.ts
│ │ │ │ │ │ │ ├── PanelVariantModalCustomOptions.tsx
│ │ │ │ │ │ │ ├── PanelVariantModalSuggestionList.tsx
│ │ │ │ │ │ │ ├── ScreenplayDisplay.tsx
│ │ │ │ │ │ │ ├── StoryboardCanvas.tsx
│ │ │ │ │ │ │ ├── StoryboardGroup.tsx
│ │ │ │ │ │ │ ├── StoryboardGroup.types.ts
│ │ │ │ │ │ │ ├── StoryboardGroupActions.tsx
│ │ │ │ │ │ │ ├── StoryboardGroupDialogs.tsx
│ │ │ │ │ │ │ ├── StoryboardGroupFailedAlert.tsx
│ │ │ │ │ │ │ ├── StoryboardGroupHeader.tsx
│ │ │ │ │ │ │ ├── StoryboardHeader.tsx
│ │ │ │ │ │ │ ├── StoryboardPanelList.tsx
│ │ │ │ │ │ │ ├── StoryboardStageShell.tsx
│ │ │ │ │ │ │ ├── StoryboardToolbar.tsx
│ │ │ │ │ │ │ ├── hooks/
│ │ │ │ │ │ │ │ ├── contracts.ts
│ │ │ │ │ │ │ │ ├── image-generation-runtime.ts
│ │ │ │ │ │ │ │ ├── panel-candidate-runtime.ts
│ │ │ │ │ │ │ │ ├── panel-operations-shared.ts
│ │ │ │ │ │ │ │ ├── panel-save-coordinator.ts
│ │ │ │ │ │ │ │ ├── storyboard-panel-asset-utils.ts
│ │ │ │ │ │ │ │ ├── storyboard-state-utils.ts
│ │ │ │ │ │ │ │ ├── useAIDataModalState.ts
│ │ │ │ │ │ │ │ ├── useImageGeneration.ts
│ │ │ │ │ │ │ │ ├── usePanelCandidates.ts
│ │ │ │ │ │ │ │ ├── usePanelCrudActions.ts
│ │ │ │ │ │ │ │ ├── usePanelEpisodeCachePatch.ts
│ │ │ │ │ │ │ │ ├── usePanelImageDownload.ts
│ │ │ │ │ │ │ │ ├── usePanelImageModification.ts
│ │ │ │ │ │ │ │ ├── usePanelImageRegeneration.ts
│ │ │ │ │ │ │ │ ├── usePanelInsertActions.ts
│ │ │ │ │ │ │ │ ├── usePanelOperations.ts
│ │ │ │ │ │ │ │ ├── usePanelVariant.ts
│ │ │ │ │ │ │ │ ├── useStoryboardAiDataRuntime.ts
│ │ │ │ │ │ │ │ ├── useStoryboardBatchPanelGeneration.ts
│ │ │ │ │ │ │ │ ├── useStoryboardGroupActions.ts
│ │ │ │ │ │ │ │ ├── useStoryboardGroupTaskErrors.ts
│ │ │ │ │ │ │ │ ├── useStoryboardInsertVariantRuntime.ts
│ │ │ │ │ │ │ │ ├── useStoryboardModalRuntime.ts
│ │ │ │ │ │ │ │ ├── useStoryboardPanelAssetActions.ts
│ │ │ │ │ │ │ │ ├── useStoryboardStageController.ts
│ │ │ │ │ │ │ │ ├── useStoryboardStageStatus.ts
│ │ │ │ │ │ │ │ ├── useStoryboardStageUiState.ts
│ │ │ │ │ │ │ │ ├── useStoryboardState.ts
│ │ │ │ │ │ │ │ └── useStoryboardTaskAwareStoryboards.ts
│ │ │ │ │ │ │ └── index.tsx
│ │ │ │ │ │ ├── video/
│ │ │ │ │ │ │ ├── FirstLastFramePanel.tsx
│ │ │ │ │ │ │ ├── VideoPanelCard.tsx
│ │ │ │ │ │ │ ├── VideoPromptModal.tsx
│ │ │ │ │ │ │ ├── VideoToolbar.tsx
│ │ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ │ ├── panel-card/
│ │ │ │ │ │ │ │ ├── VideoPanelCardBody.tsx
│ │ │ │ │ │ │ │ ├── VideoPanelCardFooter.tsx
│ │ │ │ │ │ │ │ ├── VideoPanelCardHeader.tsx
│ │ │ │ │ │ │ │ ├── VideoPanelCardLayout.tsx
│ │ │ │ │ │ │ │ ├── VideoPanelCardShell.tsx
│ │ │ │ │ │ │ │ ├── hooks/
│ │ │ │ │ │ │ │ │ └── useVideoPanelActions.tsx
│ │ │ │ │ │ │ │ ├── runtime/
│ │ │ │ │ │ │ │ │ ├── hooks/
│ │ │ │ │ │ │ │ │ │ ├── usePanelLipSync.ts
│ │ │ │ │ │ │ │ │ │ ├── usePanelPlayer.ts
│ │ │ │ │ │ │ │ │ │ ├── usePanelPromptEditor.ts
│ │ │ │ │ │ │ │ │ │ ├── usePanelTaskStatus.ts
│ │ │ │ │ │ │ │ │ │ ├── usePanelVideoModel.ts
│ │ │ │ │ │ │ │ │ │ └── usePanelVoiceManager.ts
│ │ │ │ │ │ │ │ │ ├── shared.ts
│ │ │ │ │ │ │ │ │ └── videoPanelRuntimeCore.tsx
│ │ │ │ │ │ │ │ └── types.ts
│ │ │ │ │ │ │ └── types.ts
│ │ │ │ │ │ ├── video-stage/
│ │ │ │ │ │ │ ├── VideoRenderPanel.tsx
│ │ │ │ │ │ │ ├── VideoStageLayout.tsx
│ │ │ │ │ │ │ ├── VideoStageShell.tsx
│ │ │ │ │ │ │ ├── VideoTimelinePanel.tsx
│ │ │ │ │ │ │ └── hooks/
│ │ │ │ │ │ │ └── useVideoStageRuntime.tsx
│ │ │ │ │ │ ├── voice/
│ │ │ │ │ │ │ ├── EmbeddedVoiceToolbar.tsx
│ │ │ │ │ │ │ ├── EmotionSettingsPanel.tsx
│ │ │ │ │ │ │ ├── EmptyVoiceState.tsx
│ │ │ │ │ │ │ ├── SpeakerVoiceBindingDialog.tsx
│ │ │ │ │ │ │ ├── SpeakerVoiceStatus.tsx
│ │ │ │ │ │ │ ├── VoiceDesignDialog.tsx
│ │ │ │ │ │ │ ├── VoiceLineCard.tsx
│ │ │ │ │ │ │ └── VoiceToolbar.tsx
│ │ │ │ │ │ └── voice-stage/
│ │ │ │ │ │ ├── VoiceControlPanel.tsx
│ │ │ │ │ │ ├── VoiceLineList.tsx
│ │ │ │ │ │ ├── VoiceStageLayout.tsx
│ │ │ │ │ │ ├── VoiceStageShell.tsx
│ │ │ │ │ │ └── hooks/
│ │ │ │ │ │ └── useVoiceStageRuntime.tsx
│ │ │ │ │ ├── hooks/
│ │ │ │ │ │ ├── useNovelPromotionWorkspaceController.ts
│ │ │ │ │ │ ├── useRebuildConfirm.ts
│ │ │ │ │ │ ├── useWorkspaceAssetLibraryShell.ts
│ │ │ │ │ │ ├── useWorkspaceConfigActions.ts
│ │ │ │ │ │ ├── useWorkspaceEpisodeStageData.ts
│ │ │ │ │ │ ├── useWorkspaceExecution.ts
│ │ │ │ │ │ ├── useWorkspaceModalEscape.ts
│ │ │ │ │ │ ├── useWorkspaceProjectSnapshot.ts
│ │ │ │ │ │ ├── useWorkspaceStageNavigation.ts
│ │ │ │ │ │ ├── useWorkspaceStageRuntime.ts
│ │ │ │ │ │ ├── useWorkspaceUserModels.ts
│ │ │ │ │ │ ├── useWorkspaceVideoActions.ts
│ │ │ │ │ │ └── workspace-controller-view-model.ts
│ │ │ │ │ └── types.ts
│ │ │ │ └── page.tsx
│ │ │ ├── asset-hub/
│ │ │ │ ├── components/
│ │ │ │ │ ├── AddLocationModal.tsx
│ │ │ │ │ ├── AssetGrid.tsx
│ │ │ │ │ ├── CharacterCard.tsx
│ │ │ │ │ ├── CharacterEditModal.tsx
│ │ │ │ │ ├── FolderModal.tsx
│ │ │ │ │ ├── FolderSidebar.tsx
│ │ │ │ │ ├── LocationCard.tsx
│ │ │ │ │ ├── LocationEditModal.tsx
│ │ │ │ │ ├── VoiceCard.tsx
│ │ │ │ │ ├── VoiceCreationModal.tsx
│ │ │ │ │ ├── VoiceDesignDialog.tsx
│ │ │ │ │ ├── VoicePickerDialog.tsx
│ │ │ │ │ ├── VoiceSettings.tsx
│ │ │ │ │ └── voice-creation/
│ │ │ │ │ ├── VoiceCreationForm.tsx
│ │ │ │ │ ├── VoiceCreationModalLayout.tsx
│ │ │ │ │ ├── VoiceCreationModalShell.tsx
│ │ │ │ │ ├── VoicePreviewSection.tsx
│ │ │ │ │ └── hooks/
│ │ │ │ │ └── useVoiceCreation.tsx
│ │ │ │ └── page.tsx
│ │ │ └── page.tsx
│ │ ├── api/
│ │ │ ├── admin/
│ │ │ │ └── download-logs/
│ │ │ │ └── route.ts
│ │ │ ├── asset-hub/
│ │ │ │ ├── ai-design-character/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── ai-design-location/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── ai-modify-character/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── ai-modify-location/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── appearances/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── character-voice/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── characters/
│ │ │ │ │ ├── [characterId]/
│ │ │ │ │ │ ├── appearances/
│ │ │ │ │ │ │ └── [appearanceIndex]/
│ │ │ │ │ │ │ └── route.ts
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── folders/
│ │ │ │ │ ├── [folderId]/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── generate-image/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── locations/
│ │ │ │ │ ├── [locationId]/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── modify-image/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── picker/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── reference-to-character/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── select-image/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── undo-image/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── update-asset-label/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── upload-image/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── upload-temp/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── voice-design/
│ │ │ │ │ └── route.ts
│ │ │ │ └── voices/
│ │ │ │ ├── [id]/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── route.ts
│ │ │ │ └── upload/
│ │ │ │ └── route.ts
│ │ │ ├── auth/
│ │ │ │ ├── [...nextauth]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── register/
│ │ │ │ └── route.ts
│ │ │ ├── cos/
│ │ │ │ └── image/
│ │ │ │ └── route.ts
│ │ │ ├── files/
│ │ │ │ └── [...path]/
│ │ │ │ └── route.ts
│ │ │ ├── novel-promotion/
│ │ │ │ └── [projectId]/
│ │ │ │ ├── ai-create-character/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── ai-create-location/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── ai-modify-appearance/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── ai-modify-location/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── ai-modify-shot-prompt/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── analyze/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── analyze-global/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── analyze-shot-variants/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── assets/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── character/
│ │ │ │ │ ├── appearance/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── confirm-selection/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── character-profile/
│ │ │ │ │ ├── batch-confirm/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── confirm/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── character-voice/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── cleanup-unselected-images/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── clips/
│ │ │ │ │ ├── [clipId]/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── copy-from-global/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── download-images/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── download-videos/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── download-voices/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── editor/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── episodes/
│ │ │ │ │ ├── [episodeId]/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── batch/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── route.ts
│ │ │ │ │ ├── split/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── split-by-markers/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── generate-character-image/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── generate-image/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── generate-video/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── insert-panel/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── lip-sync/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── location/
│ │ │ │ │ ├── confirm-selection/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── modify-asset-image/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── modify-storyboard-image/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── panel/
│ │ │ │ │ ├── route.ts
│ │ │ │ │ └── select-candidate/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── panel-link/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── panel-variant/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── photography-plan/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── reference-to-character/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── regenerate-group/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── regenerate-panel-image/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── regenerate-single-image/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── regenerate-storyboard-text/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── route.ts
│ │ │ │ ├── screenplay-conversion/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── script-to-storyboard-stream/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── select-character-image/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── select-location-image/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── speaker-voice/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── story-to-script-stream/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── storyboard-group/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── storyboards/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── undo-regenerate/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── update-appearance/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── update-asset-label/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── update-location/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── update-prompt/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── upload-asset-image/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── video-proxy/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── video-urls/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── voice-analyze/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── voice-design/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── voice-generate/
│ │ │ │ │ └── route.ts
│ │ │ │ └── voice-lines/
│ │ │ │ └── route.ts
│ │ │ ├── projects/
│ │ │ │ ├── [projectId]/
│ │ │ │ │ ├── assets/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── costs/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── data/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── runs/
│ │ │ │ ├── [runId]/
│ │ │ │ │ ├── cancel/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── events/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── route.ts
│ │ │ │ │ └── steps/
│ │ │ │ │ └── [stepKey]/
│ │ │ │ │ └── retry/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── sse/
│ │ │ │ └── route.ts
│ │ │ ├── storage/
│ │ │ │ └── sign/
│ │ │ │ └── route.ts
│ │ │ ├── system/
│ │ │ │ └── boot-id/
│ │ │ │ └── route.ts
│ │ │ ├── task-target-states/
│ │ │ │ └── route.ts
│ │ │ ├── tasks/
│ │ │ │ ├── [taskId]/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── dismiss/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── user/
│ │ │ │ ├── api-config/
│ │ │ │ │ ├── assistant/
│ │ │ │ │ │ ├── probe-media-template/
│ │ │ │ │ │ │ └── route.ts
│ │ │ │ │ │ └── validate-media-template/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── probe-model-llm-protocol/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ ├── route.ts
│ │ │ │ │ ├── test-connection/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── test-provider/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── assistant/
│ │ │ │ │ └── chat/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── balance/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── costs/
│ │ │ │ │ ├── details/
│ │ │ │ │ │ └── route.ts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── models/
│ │ │ │ │ └── route.ts
│ │ │ │ └── transactions/
│ │ │ │ └── route.ts
│ │ │ └── user-preference/
│ │ │ └── route.ts
│ │ ├── globals.css
│ │ └── m/
│ │ └── [publicId]/
│ │ └── route.ts
│ ├── components/
│ │ ├── ConfirmDialog.tsx
│ │ ├── LanguageSwitcher.tsx
│ │ ├── Navbar.tsx
│ │ ├── ProgressToast.tsx
│ │ ├── UpdateNoticeModal.tsx
│ │ ├── ai-elements/
│ │ │ ├── conversation.tsx
│ │ │ ├── message.tsx
│ │ │ ├── reasoning.tsx
│ │ │ └── tool.tsx
│ │ ├── assistant/
│ │ │ ├── AssistantChatModal.tsx
│ │ │ └── useAssistantChat.ts
│ │ ├── auth/
│ │ │ └── PasswordStrengthIndicator.tsx
│ │ ├── image-generation/
│ │ │ ├── ImageGenerationInlineCountButton.tsx
│ │ │ └── ImageGenerationSlotOverlay.tsx
│ │ ├── llm-console/
│ │ │ ├── LLMStageStreamCard.tsx
│ │ │ └── index.ts
│ │ ├── media/
│ │ │ ├── MediaImage.tsx
│ │ │ └── MediaImageWithLoading.tsx
│ │ ├── providers/
│ │ │ └── QueryProvider.tsx
│ │ ├── shared/
│ │ │ └── assets/
│ │ │ ├── CharacterCreationModal.tsx
│ │ │ ├── CharacterEditModal.tsx
│ │ │ ├── GlobalAssetPicker.tsx
│ │ │ ├── LocationCreationModal.tsx
│ │ │ ├── LocationEditModal.tsx
│ │ │ ├── character-creation/
│ │ │ │ ├── CharacterCreationForm.tsx
│ │ │ │ ├── CharacterCreationPreview.tsx
│ │ │ │ └── hooks/
│ │ │ │ └── useCharacterCreationSubmit.ts
│ │ │ └── index.ts
│ │ ├── task/
│ │ │ ├── TaskStatusInline.tsx
│ │ │ └── TaskStatusOverlay.tsx
│ │ ├── ui/
│ │ │ ├── CapsuleNav.tsx
│ │ │ ├── ConfigModals.tsx
│ │ │ ├── ImagePreviewModal.tsx
│ │ │ ├── SegmentedControl.tsx
│ │ │ ├── SharedComponents.tsx
│ │ │ ├── ai-edit-style.ts
│ │ │ ├── config-modals/
│ │ │ │ ├── ConfigConfirmModal.tsx
│ │ │ │ ├── ConfigDeleteModal.tsx
│ │ │ │ ├── ConfigEditModal.tsx
│ │ │ │ ├── ModelCapabilityDropdown.tsx
│ │ │ │ ├── WorldContextModal.tsx
│ │ │ │ └── config-modal-selectors.tsx
│ │ │ ├── icons/
│ │ │ │ ├── AISparklesIcon.tsx
│ │ │ │ ├── AppIcon.tsx
│ │ │ │ ├── RatioPreviewIcon.tsx
│ │ │ │ ├── custom.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── registry.ts
│ │ │ ├── model-dropdown-innovative.tsx
│ │ │ ├── model-dropdown-ios.tsx
│ │ │ ├── model-dropdown-variants.tsx
│ │ │ ├── patterns/
│ │ │ │ ├── PanelCardV2.tsx
│ │ │ │ ├── PanelEditFormV2.tsx
│ │ │ │ ├── StoryboardHeaderV2.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ ├── primitives/
│ │ │ │ ├── GlassButton.tsx
│ │ │ │ ├── GlassChip.tsx
│ │ │ │ ├── GlassField.tsx
│ │ │ │ ├── GlassInput.tsx
│ │ │ │ ├── GlassModalShell.tsx
│ │ │ │ ├── GlassSurface.tsx
│ │ │ │ ├── GlassTextarea.tsx
│ │ │ │ └── index.ts
│ │ │ └── select-variants.tsx
│ │ └── voice/
│ │ ├── VoiceDesignDialogBase.tsx
│ │ ├── VoiceDesignGeneratorSection.tsx
│ │ └── voice-design-shared.ts
│ ├── contexts/
│ │ └── ToastContext.tsx
│ ├── features/
│ │ └── video-editor/
│ │ ├── components/
│ │ │ ├── Preview/
│ │ │ │ ├── RemotionPreview.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Timeline/
│ │ │ │ ├── Timeline.tsx
│ │ │ │ └── index.ts
│ │ │ ├── TransitionPicker.tsx
│ │ │ └── VideoEditorStage.tsx
│ │ ├── hooks/
│ │ │ ├── useEditorActions.ts
│ │ │ └── useEditorState.ts
│ │ ├── index.ts
│ │ ├── remotion/
│ │ │ ├── VideoComposition.tsx
│ │ │ └── transitions/
│ │ │ └── index.tsx
│ │ ├── types/
│ │ │ └── editor.types.ts
│ │ └── utils/
│ │ ├── migration.ts
│ │ └── time-utils.ts
│ ├── hooks/
│ │ └── common/
│ │ ├── useCandidateSystem.ts
│ │ └── useGithubReleaseUpdate.ts
│ ├── i18n/
│ │ ├── navigation.ts
│ │ └── routing.ts
│ ├── i18n.ts
│ ├── instrumentation.ts
│ ├── lib/
│ │ ├── ai-runtime/
│ │ │ ├── client.ts
│ │ │ ├── errors.ts
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ ├── api-auth.ts
│ │ ├── api-config.ts
│ │ ├── api-errors.ts
│ │ ├── api-fetch.ts
│ │ ├── app-meta.ts
│ │ ├── ark-api.ts
│ │ ├── ark-llm.ts
│ │ ├── asset-utils/
│ │ │ ├── ai-design.ts
│ │ │ └── index.ts
│ │ ├── assets/
│ │ │ └── description-fields.ts
│ │ ├── assistant-platform/
│ │ │ ├── errors.ts
│ │ │ ├── index.ts
│ │ │ ├── registry.ts
│ │ │ ├── runtime.ts
│ │ │ ├── skills/
│ │ │ │ ├── api-config-template.ts
│ │ │ │ └── tutorial.ts
│ │ │ ├── system-prompts.ts
│ │ │ └── types.ts
│ │ ├── async/
│ │ │ └── map-with-concurrency.ts
│ │ ├── async-poll.ts
│ │ ├── async-submit.ts
│ │ ├── async-task-utils.ts
│ │ ├── auth.ts
│ │ ├── billing/
│ │ │ ├── cost.ts
│ │ │ ├── currency.ts
│ │ │ ├── errors.ts
│ │ │ ├── index.ts
│ │ │ ├── ledger.ts
│ │ │ ├── mode.ts
│ │ │ ├── money.ts
│ │ │ ├── reporting.ts
│ │ │ ├── runtime-usage.ts
│ │ │ ├── service.ts
│ │ │ ├── task-policy.ts
│ │ │ └── types.ts
│ │ ├── config-service.ts
│ │ ├── constants.ts
│ │ ├── contracts/
│ │ │ ├── image-urls-contract.test.ts
│ │ │ └── image-urls-contract.ts
│ │ ├── crypto-utils.ts
│ │ ├── env.ts
│ │ ├── episode-marker-detector.ts
│ │ ├── error-handler.ts
│ │ ├── error-utils.ts
│ │ ├── errors/
│ │ │ ├── codes.ts
│ │ │ ├── display.ts
│ │ │ ├── extract.ts
│ │ │ ├── normalize.ts
│ │ │ ├── types.ts
│ │ │ └── user-messages.ts
│ │ ├── fonts.ts
│ │ ├── gemini-batch-utils.ts
│ │ ├── generator-api.ts
│ │ ├── generators/
│ │ │ ├── ark.ts
│ │ │ ├── audio/
│ │ │ │ ├── bailian.ts
│ │ │ │ └── index.ts
│ │ │ ├── base.ts
│ │ │ ├── factory.ts
│ │ │ ├── fal.ts
│ │ │ ├── image/
│ │ │ │ ├── gemini-compatible.ts
│ │ │ │ ├── google.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── openai-compatible.ts
│ │ │ ├── minimax.ts
│ │ │ ├── official.ts
│ │ │ ├── resolution-adapter.ts
│ │ │ ├── video/
│ │ │ │ ├── google.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── openai-compatible.ts
│ │ │ └── vidu.ts
│ │ ├── image-cache.ts
│ │ ├── image-generation/
│ │ │ ├── count-preference.ts
│ │ │ ├── count.ts
│ │ │ ├── location-slots.ts
│ │ │ ├── slot-state.ts
│ │ │ └── use-image-generation-count.ts
│ │ ├── image-label.ts
│ │ ├── json-repair.ts
│ │ ├── lipsync/
│ │ │ ├── index.ts
│ │ │ ├── preprocess.ts
│ │ │ ├── providers/
│ │ │ │ ├── bailian.ts
│ │ │ │ ├── fal.ts
│ │ │ │ └── vidu.ts
│ │ │ └── types.ts
│ │ ├── llm/
│ │ │ ├── chat-completion.ts
│ │ │ ├── chat-stream.ts
│ │ │ ├── completion-parts.ts
│ │ │ ├── index.ts
│ │ │ ├── providers/
│ │ │ │ ├── ark.ts
│ │ │ │ ├── google.ts
│ │ │ │ └── openai-compat.ts
│ │ │ ├── reasoning-capability.ts
│ │ │ ├── runtime-shared.ts
│ │ │ ├── runtime.ts
│ │ │ ├── stream-helpers.ts
│ │ │ ├── stream-timeout.ts
│ │ │ ├── types.ts
│ │ │ ├── utils.ts
│ │ │ └── vision.ts
│ │ ├── llm-client.ts
│ │ ├── llm-observe/
│ │ │ ├── config.ts
│ │ │ ├── internal-stream-context.ts
│ │ │ ├── internal-task.ts
│ │ │ ├── route-task.ts
│ │ │ ├── stage-pipeline.ts
│ │ │ ├── task-policy.ts
│ │ │ └── types.ts
│ │ ├── logging/
│ │ │ ├── config.ts
│ │ │ ├── context.ts
│ │ │ ├── core.ts
│ │ │ ├── file-writer.ts
│ │ │ ├── redact.ts
│ │ │ ├── semantic.ts
│ │ │ └── types.ts
│ │ ├── media/
│ │ │ ├── attach.ts
│ │ │ ├── hash.ts
│ │ │ ├── image-url.test.ts
│ │ │ ├── image-url.ts
│ │ │ ├── outbound-image.test.ts
│ │ │ ├── outbound-image.ts
│ │ │ ├── service.ts
│ │ │ └── types.ts
│ │ ├── media-process.ts
│ │ ├── migrations/
│ │ │ └── gateway-route-openai-compat.ts
│ │ ├── model-capabilities/
│ │ │ ├── catalog.ts
│ │ │ ├── lookup.ts
│ │ │ ├── video-effective.ts
│ │ │ └── video-model-options.ts
│ │ ├── model-config-contract.ts
│ │ ├── model-gateway/
│ │ │ ├── index.ts
│ │ │ ├── llm.ts
│ │ │ ├── openai-compat/
│ │ │ │ ├── chat.ts
│ │ │ │ ├── common.ts
│ │ │ │ ├── image.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── responses.ts
│ │ │ │ ├── template-image.ts
│ │ │ │ ├── template-video.ts
│ │ │ │ └── video.ts
│ │ │ ├── router.ts
│ │ │ └── types.ts
│ │ ├── model-pricing/
│ │ │ ├── catalog.ts
│ │ │ ├── lookup.ts
│ │ │ ├── version.ts
│ │ │ └── video-tier.ts
│ │ ├── modes.ts
│ │ ├── novel-promotion/
│ │ │ ├── insert-panel.ts
│ │ │ ├── panel-ai-data-sync.ts
│ │ │ ├── run-stream/
│ │ │ │ └── types.ts
│ │ │ ├── script-to-storyboard/
│ │ │ │ └── orchestrator.ts
│ │ │ ├── stages/
│ │ │ │ ├── contracts/
│ │ │ │ │ ├── video-stage-contract.ts
│ │ │ │ │ └── voice-stage-contract.ts
│ │ │ │ ├── video-stage-runtime/
│ │ │ │ │ ├── immediate-video-submission.ts
│ │ │ │ │ ├── task-targets.ts
│ │ │ │ │ ├── types.ts
│ │ │ │ │ ├── useVideoDownloadAll.ts
│ │ │ │ │ ├── useVideoFirstLastFrameFlow.ts
│ │ │ │ │ ├── useVideoPanelLinking.ts
│ │ │ │ │ ├── useVideoPanelViewport.ts
│ │ │ │ │ ├── useVideoPanelsProjection.ts
│ │ │ │ │ ├── useVideoPromptState.ts
│ │ │ │ │ ├── useVideoStageUiState.ts
│ │ │ │ │ ├── useVideoTaskStates.ts
│ │ │ │ │ ├── useVideoVoiceLines.ts
│ │ │ │ │ └── utils.ts
│ │ │ │ ├── video-stage-runtime-core.tsx
│ │ │ │ ├── voice-stage-runtime/
│ │ │ │ │ ├── types.ts
│ │ │ │ │ ├── useBindablePanelOptions.ts
│ │ │ │ │ ├── useSpeakerAssetNavigation.ts
│ │ │ │ │ ├── useVoiceGenerationActions.ts
│ │ │ │ │ ├── useVoiceLineBindings.ts
│ │ │ │ │ ├── useVoiceLineCrudActions.ts
│ │ │ │ │ ├── useVoiceLineEditorState.ts
│ │ │ │ │ ├── useVoicePlayback.ts
│ │ │ │ │ ├── useVoiceRuntimeSync.ts
│ │ │ │ │ ├── useVoiceSpeakerState.ts
│ │ │ │ │ ├── useVoiceStageDataLoader.ts
│ │ │ │ │ ├── useVoiceTaskState.ts
│ │ │ │ │ └── utils.ts
│ │ │ │ └── voice-stage-runtime-core.tsx
│ │ │ └── story-to-script/
│ │ │ ├── clip-matching.ts
│ │ │ ├── orchestrator.ts
│ │ │ └── types.ts
│ │ ├── openai-compat-media-template.ts
│ │ ├── openai-compat-template-runtime.ts
│ │ ├── prisma-error.ts
│ │ ├── prisma-retry.ts
│ │ ├── prisma.ts
│ │ ├── prompt-i18n/
│ │ │ ├── build-prompt.ts
│ │ │ ├── catalog.ts
│ │ │ ├── errors.ts
│ │ │ ├── index.ts
│ │ │ ├── prompt-ids.ts
│ │ │ ├── template-store.ts
│ │ │ └── types.ts
│ │ ├── providers/
│ │ │ ├── bailian/
│ │ │ │ ├── audio.ts
│ │ │ │ ├── catalog.ts
│ │ │ │ ├── image.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── llm.ts
│ │ │ │ ├── probe.ts
│ │ │ │ ├── tts.ts
│ │ │ │ ├── types.ts
│ │ │ │ ├── video.ts
│ │ │ │ ├── voice-cleanup.ts
│ │ │ │ ├── voice-design.ts
│ │ │ │ └── voice-manage.ts
│ │ │ ├── fal/
│ │ │ │ └── base-url.ts
│ │ │ ├── official/
│ │ │ │ └── model-registry.ts
│ │ │ └── siliconflow/
│ │ │ ├── audio.ts
│ │ │ ├── catalog.ts
│ │ │ ├── image.ts
│ │ │ ├── index.ts
│ │ │ ├── llm.ts
│ │ │ ├── probe.ts
│ │ │ ├── types.ts
│ │ │ └── video.ts
│ │ ├── query/
│ │ │ ├── client.ts
│ │ │ ├── hooks/
│ │ │ │ ├── index.ts
│ │ │ │ ├── run-stream/
│ │ │ │ │ ├── event-parser.ts
│ │ │ │ │ ├── recovered-run-subscription.ts
│ │ │ │ │ ├── run-event-adapter.ts
│ │ │ │ │ ├── run-request-executor.ts
│ │ │ │ │ ├── run-stream-sse-body.ts
│ │ │ │ │ ├── run-stream-state-runtime.ts
│ │ │ │ │ ├── run-stream-view.ts
│ │ │ │ │ ├── snapshot.ts
│ │ │ │ │ ├── state-machine.ts
│ │ │ │ │ └── types.ts
│ │ │ │ ├── useGlobalAssets.ts
│ │ │ │ ├── useProjectAssets.ts
│ │ │ │ ├── useProjectData.ts
│ │ │ │ ├── useRunStreamState.ts
│ │ │ │ ├── useSSE.ts
│ │ │ │ ├── useScriptToStoryboardRunStream.ts
│ │ │ │ ├── useStoryToScriptRunStream.ts
│ │ │ │ ├── useStoryboards.ts
│ │ │ │ ├── useTaskPresentation.ts
│ │ │ │ ├── useTaskStatus.ts
│ │ │ │ ├── useTaskTargetStateMap.ts
│ │ │ │ ├── useUserModels.ts
│ │ │ │ └── useVoiceLines.ts
│ │ │ ├── keys.ts
│ │ │ ├── mutations/
│ │ │ │ ├── asset-hub-character-mutations.ts
│ │ │ │ ├── asset-hub-creation-mutations.ts
│ │ │ │ ├── asset-hub-location-mutations.ts
│ │ │ │ ├── asset-hub-mutations-runtime.ts
│ │ │ │ ├── asset-hub-mutations-shared.ts
│ │ │ │ ├── asset-hub-update-mutations.ts
│ │ │ │ ├── asset-hub-voice-mutations.ts
│ │ │ │ ├── character-base-mutations.ts
│ │ │ │ ├── character-image-ops-mutations.ts
│ │ │ │ ├── character-profile-mutations.ts
│ │ │ │ ├── character-voice-mutations.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── location-image-mutations.ts
│ │ │ │ ├── location-management-mutations.ts
│ │ │ │ ├── mutation-shared.ts
│ │ │ │ ├── storyboard-panel-mutations.ts
│ │ │ │ ├── storyboard-prompt-mutations.ts
│ │ │ │ ├── task-mutations.ts
│ │ │ │ ├── useAssetHubMutations.ts
│ │ │ │ ├── useCharacterMutations.core.ts
│ │ │ │ ├── useCharacterMutations.ts
│ │ │ │ ├── useEpisodeMutations.ts
│ │ │ │ ├── useLocationMutations.core.ts
│ │ │ │ ├── useLocationMutations.ts
│ │ │ │ ├── useProjectConfigMutations.ts
│ │ │ │ ├── useProjectMutations.ts
│ │ │ │ ├── useStoryboardMutations.core.ts
│ │ │ │ ├── useStoryboardMutations.ts
│ │ │ │ ├── useVideoMutations.ts
│ │ │ │ └── useVoiceMutations.ts
│ │ │ └── task-target-overlay.ts
│ │ ├── rate-limit.ts
│ │ ├── redis.ts
│ │ ├── run-runtime/
│ │ │ ├── publisher.ts
│ │ │ ├── service.ts
│ │ │ ├── task-bridge.ts
│ │ │ ├── types.ts
│ │ │ ├── workflow-lease.ts
│ │ │ └── workflow.ts
│ │ ├── server-boot.ts
│ │ ├── srt.ts
│ │ ├── sse/
│ │ │ └── shared-subscriber.ts
│ │ ├── storage/
│ │ │ ├── bootstrap.ts
│ │ │ ├── errors.ts
│ │ │ ├── factory.ts
│ │ │ ├── index.ts
│ │ │ ├── init.ts
│ │ │ ├── providers/
│ │ │ │ ├── cos.ts
│ │ │ │ ├── local.ts
│ │ │ │ └── minio.ts
│ │ │ ├── signed-urls.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ ├── storyboard-phases.ts
│ │ ├── task/
│ │ │ ├── client.ts
│ │ │ ├── error-message.ts
│ │ │ ├── errors.ts
│ │ │ ├── has-output.ts
│ │ │ ├── intent.ts
│ │ │ ├── presentation.ts
│ │ │ ├── progress-message.ts
│ │ │ ├── publisher.ts
│ │ │ ├── queues.ts
│ │ │ ├── reconcile.ts
│ │ │ ├── resolve-locale.ts
│ │ │ ├── service.ts
│ │ │ ├── state-service.ts
│ │ │ ├── submitter.ts
│ │ │ ├── types.ts
│ │ │ └── ui-payload.ts
│ │ ├── update-check.ts
│ │ ├── user-api/
│ │ │ ├── llm-test-connection.ts
│ │ │ ├── model-llm-protocol-probe.ts
│ │ │ ├── model-template/
│ │ │ │ ├── index.ts
│ │ │ │ ├── probe.ts
│ │ │ │ ├── save.ts
│ │ │ │ ├── schema.ts
│ │ │ │ └── validator.ts
│ │ │ └── provider-test.ts
│ │ ├── voice/
│ │ │ ├── generate-voice-line.ts
│ │ │ └── provider-voice-binding.ts
│ │ ├── word-count.ts
│ │ ├── workers/
│ │ │ ├── handlers/
│ │ │ │ ├── analyze-global-parse.ts
│ │ │ │ ├── analyze-global-persist.ts
│ │ │ │ ├── analyze-global-prompt.ts
│ │ │ │ ├── analyze-global.ts
│ │ │ │ ├── analyze-novel.ts
│ │ │ │ ├── asset-hub-ai-design.ts
│ │ │ │ ├── asset-hub-ai-modify.ts
│ │ │ │ ├── asset-hub-image-task-handler.ts
│ │ │ │ ├── asset-hub-modify-task-handler.ts
│ │ │ │ ├── character-image-task-handler.ts
│ │ │ │ ├── character-profile-helpers.ts
│ │ │ │ ├── character-profile.ts
│ │ │ │ ├── clips-build.ts
│ │ │ │ ├── episode-split.ts
│ │ │ │ ├── image-task-handler-shared.ts
│ │ │ │ ├── image-task-handlers-core.ts
│ │ │ │ ├── image-task-handlers.ts
│ │ │ │ ├── llm-proxy.ts
│ │ │ │ ├── llm-stream.ts
│ │ │ │ ├── location-image-task-handler.ts
│ │ │ │ ├── modify-asset-image-task-handler.ts
│ │ │ │ ├── modify-description-sync.ts
│ │ │ │ ├── panel-image-task-handler.ts
│ │ │ │ ├── panel-variant-task-handler.ts
│ │ │ │ ├── reference-to-character-helpers.ts
│ │ │ │ ├── reference-to-character.ts
│ │ │ │ ├── resolve-analysis-model.ts
│ │ │ │ ├── screenplay-convert-helpers.ts
│ │ │ │ ├── screenplay-convert.ts
│ │ │ │ ├── script-to-storyboard-atomic-retry.ts
│ │ │ │ ├── script-to-storyboard-helpers.ts
│ │ │ │ ├── script-to-storyboard.ts
│ │ │ │ ├── shot-ai-persist.ts
│ │ │ │ ├── shot-ai-prompt-appearance.ts
│ │ │ │ ├── shot-ai-prompt-location.ts
│ │ │ │ ├── shot-ai-prompt-runtime.ts
│ │ │ │ ├── shot-ai-prompt-shot.ts
│ │ │ │ ├── shot-ai-prompt-utils.ts
│ │ │ │ ├── shot-ai-prompt.ts
│ │ │ │ ├── shot-ai-tasks.ts
│ │ │ │ ├── shot-ai-variants.ts
│ │ │ │ ├── story-to-script-helpers.ts
│ │ │ │ ├── story-to-script.ts
│ │ │ │ ├── voice-analyze-helpers.ts
│ │ │ │ ├── voice-analyze.ts
│ │ │ │ └── voice-design.ts
│ │ │ ├── image.worker.ts
│ │ │ ├── index.ts
│ │ │ ├── shared.ts
│ │ │ ├── text.worker.ts
│ │ │ ├── user-concurrency-gate.ts
│ │ │ ├── utils.ts
│ │ │ ├── video.worker.ts
│ │ │ └── voice.worker.ts
│ │ ├── workflow-concurrency.ts
│ │ ├── workflow-engine/
│ │ │ └── dependencies.ts
│ │ └── workspace/
│ │ └── model-setup.ts
│ ├── middleware.ts
│ ├── pages/
│ │ └── _document.tsx
│ ├── styles/
│ │ ├── animations.css
│ │ ├── ui-semantic-glass.css
│ │ └── ui-tokens-glass.css
│ └── types/
│ ├── character-profile.ts
│ ├── next-auth.d.ts
│ ├── project.ts
│ └── storyboard-types.ts
├── standards/
│ ├── capabilities/
│ │ ├── catalog.example.json
│ │ └── image-video.catalog.json
│ ├── pricing/
│ │ └── image-video.pricing.json
│ └── prompt-canary/
│ ├── screenplay_conversion.canary.json
│ ├── story_to_script_clips.canary.json
│ ├── storyboard_panels.canary.json
│ └── voice_analysis.canary.json
├── tests/
│ ├── concurrency/
│ │ └── billing/
│ │ └── ledger.concurrency.test.ts
│ ├── contracts/
│ │ ├── behavior-test-standard.md
│ │ ├── requirements-matrix.test.ts
│ │ ├── requirements-matrix.ts
│ │ ├── route-behavior-matrix.ts
│ │ ├── route-catalog.ts
│ │ ├── task-type-catalog.ts
│ │ └── tasktype-behavior-matrix.ts
│ ├── fixtures/
│ │ └── billing/
│ │ └── cases.json
│ ├── helpers/
│ │ ├── assertions.ts
│ │ ├── auth.ts
│ │ ├── billing-fixtures.ts
│ │ ├── db-reset.ts
│ │ ├── fakes/
│ │ │ ├── llm.ts
│ │ │ ├── media.ts
│ │ │ ├── providers.ts
│ │ │ └── scenario-server.ts
│ │ ├── fixtures.ts
│ │ ├── mock-query-client.ts
│ │ ├── prisma.ts
│ │ └── request.ts
│ ├── hidden/
│ │ └── README.md
│ ├── integration/
│ │ ├── api/
│ │ │ ├── contract/
│ │ │ │ ├── crud-routes.test.ts
│ │ │ │ ├── direct-submit-routes.test.ts
│ │ │ │ ├── infra-routes.test.ts
│ │ │ │ ├── llm-observe-routes.test.ts
│ │ │ │ ├── run-cancel.route.test.ts
│ │ │ │ ├── run-step-retry.route.test.ts
│ │ │ │ └── task-infra-routes.test.ts
│ │ │ ├── helpers/
│ │ │ │ └── call-route.ts
│ │ │ └── specific/
│ │ │ ├── asset-hub-appearances-route.test.ts
│ │ │ ├── asset-hub-generate-image-art-style.test.ts
│ │ │ ├── asset-hub-location-create-no-auto-generate.test.ts
│ │ │ ├── characters-post-reference-forwarding.test.ts
│ │ │ ├── characters-post.test.ts
│ │ │ ├── novel-promotion-character-style-forwarding.test.ts
│ │ │ ├── novel-promotion-generate-image-art-style.test.ts
│ │ │ ├── novel-promotion-location-style-forwarding.test.ts
│ │ │ ├── novel-promotion-project-art-style-validation.test.ts
│ │ │ ├── panel-variant-route.test.ts
│ │ │ ├── project-create-default-audio-model.test.ts
│ │ │ ├── reference-to-character-api.test.ts
│ │ │ ├── speaker-voice-provider-contract.test.ts
│ │ │ ├── user-api-config-probe-model-llm-protocol.test.ts
│ │ │ ├── user-api-config-put.test.ts
│ │ │ ├── user-assistant-chat-api-config.test.ts
│ │ │ ├── user-models-audio-filter.test.ts
│ │ │ ├── user-preference-art-style-validation.test.ts
│ │ │ └── voice-generate-default-audio-model.test.ts
│ │ ├── billing/
│ │ │ ├── api-contract.integration.test.ts
│ │ │ ├── ledger.integration.test.ts
│ │ │ ├── service.integration.test.ts
│ │ │ ├── submitter.integration.test.ts
│ │ │ └── worker-lifecycle.integration.test.ts
│ │ ├── chain/
│ │ │ ├── image.chain.test.ts
│ │ │ ├── text.chain.test.ts
│ │ │ ├── video.chain.test.ts
│ │ │ └── voice.chain.test.ts
│ │ ├── provider/
│ │ │ ├── fal-provider.contract.test.ts
│ │ │ └── openai-compat-provider.contract.test.ts
│ │ ├── run-runtime/
│ │ │ └── retry-failed-step.integration.test.ts
│ │ └── task/
│ │ └── create-task-dedupe.integration.test.ts
│ ├── regression/
│ │ ├── panel-variant-cross-storyboard.test.ts
│ │ ├── task-dedupe-recovery.test.ts
│ │ └── task-enqueue-billing-rollback.test.ts
│ ├── setup/
│ │ ├── env.ts
│ │ ├── global-setup.ts
│ │ └── global-teardown.ts
│ ├── system/
│ │ ├── generate-image.system.test.ts
│ │ ├── generate-video.system.test.ts
│ │ ├── helpers/
│ │ │ ├── seed.ts
│ │ │ ├── tasks.ts
│ │ │ └── workers.ts
│ │ ├── text-workflow.system.test.ts
│ │ └── voice-generate.system.test.ts
│ └── unit/
│ ├── ai-runtime/
│ │ └── errors.test.ts
│ ├── api-config/
│ │ ├── assistant-chat-modal-content.test.ts
│ │ ├── minimax-preset.test.ts
│ │ ├── preset-coming-soon.test.ts
│ │ ├── provider-card-assistant-saved-label.test.ts
│ │ ├── provider-card-pricing-form.test.ts
│ │ ├── provider-card-protocol-probe.test.ts
│ │ ├── provider-card-shell.test.ts
│ │ ├── provider-card-tutorial-modal.test.ts
│ │ ├── use-api-config-filters.test.ts
│ │ ├── use-assistant-chat-saved-events.test.ts
│ │ └── use-providers-order.test.ts
│ ├── assistant-platform/
│ │ ├── registry.test.ts
│ │ ├── runtime.test.ts
│ │ ├── skills-api-config-template.test.ts
│ │ └── system-prompts.test.ts
│ ├── async-poll-ocompat.test.ts
│ ├── billing/
│ │ ├── cost-error-branches.test.ts
│ │ ├── cost.test.ts
│ │ ├── ledger-extra.test.ts
│ │ ├── mode.test.ts
│ │ ├── runtime-usage.test.ts
│ │ ├── service.test.ts
│ │ └── task-policy.test.ts
│ ├── components/
│ │ ├── character-creation-modal.test.ts
│ │ ├── image-generation-inline-count-button.test.ts
│ │ ├── llm-stage-stream-card-error.test.ts
│ │ ├── location-creation-modal.test.ts
│ │ ├── navbar-download-logs.test.ts
│ │ └── voice-design-shared.test.ts
│ ├── generator-api-openai-template-required.test.ts
│ ├── generator-api.test.ts
│ ├── generators/
│ │ ├── factory.test.ts
│ │ ├── fal-video-kling-presets.test.ts
│ │ ├── image-provider-smoke.test.ts
│ │ ├── openai-compatible-image.test.ts
│ │ └── openai-compatible-video.test.ts
│ ├── guards/
│ │ ├── api-route-contract-guard.test.ts
│ │ ├── changed-file-test-impact-guard.test.ts
│ │ ├── image-reference-normalization-guard.test.ts
│ │ └── task-submit-compensation-guard.test.ts
│ ├── helpers/
│ │ ├── api-fetch.test.ts
│ │ ├── json-repair.test.ts
│ │ ├── llm-stage-stream-card-output.test.ts
│ │ ├── logging-core.test.ts
│ │ ├── migrate-gateway-route-openai-compat.test.ts
│ │ ├── prompt-suffix-regression.test.ts
│ │ ├── recovered-run-subscription.test.ts
│ │ ├── reference-to-character-helpers.test.ts
│ │ ├── route-task-helpers.test.ts
│ │ ├── run-request-executor.run-events.test.ts
│ │ ├── run-stream-state-machine.test.ts
│ │ ├── run-stream-view.test.ts
│ │ ├── task-state-service.test.ts
│ │ ├── task-submitter-helpers.test.ts
│ │ ├── update-check.test.ts
│ │ └── workspace-model-setup.test.ts
│ ├── image-generation/
│ │ ├── count.test.ts
│ │ └── slot-state.test.ts
│ ├── lipsync-bailian.test.ts
│ ├── lipsync-preprocess.test.ts
│ ├── llm/
│ │ ├── ark-llm-thinking.test.ts
│ │ ├── chat-completion-official-provider.test.ts
│ │ ├── chat-completion-openai-compatible-protocol.test.ts
│ │ ├── chat-stream-official-provider.test.ts
│ │ ├── chat-stream-openai-compatible-protocol.test.ts
│ │ ├── completion-parts-think-tag.test.ts
│ │ └── reasoning-capability.test.ts
│ ├── model-capabilities/
│ │ ├── bailian-video-capabilities.test.ts
│ │ ├── image-resolution-default.test.ts
│ │ └── video-effective.test.ts
│ ├── model-gateway/
│ │ ├── llm.test.ts
│ │ ├── openai-compat-responses.test.ts
│ │ ├── openai-compat-template-image-output-urls.test.ts
│ │ ├── openai-compat-template-renderer.test.ts
│ │ ├── openai-compat-template-video-external-id.test.ts
│ │ └── router.test.ts
│ ├── novel-promotion/
│ │ ├── character-voice-mutations.test.ts
│ │ ├── immediate-video-submission.test.ts
│ │ ├── insert-panel-user-input.test.ts
│ │ ├── panel-task-status-error-code.test.ts
│ │ ├── use-tts-generation.test.ts
│ │ ├── video-model-options.test.ts
│ │ ├── video-panel-card-body.test.ts
│ │ ├── video-panels-projection-error-code.test.ts
│ │ ├── voice-generation-actions.test.ts
│ │ ├── voice-runtime-sync.test.ts
│ │ ├── voice-stage-data-loader.test.ts
│ │ └── workspace-video-actions.test.ts
│ ├── optimistic/
│ │ ├── ai-data-modal-state.test.ts
│ │ ├── asset-hub-mutations.test.ts
│ │ ├── panel-ai-data-sync.test.ts
│ │ ├── panel-save-coordinator.test.ts
│ │ ├── project-asset-mutations.test.ts
│ │ ├── sse-invalidation.test.ts
│ │ ├── task-target-overlay.test.ts
│ │ └── task-target-state-map.test.ts
│ ├── providers/
│ │ ├── bailian-llm.test.ts
│ │ ├── bailian-tts.test.ts
│ │ ├── bailian-video.test.ts
│ │ ├── bailian-voice-cleanup.test.ts
│ │ ├── bailian-voice-design.test.ts
│ │ └── model-registry.test.ts
│ ├── query/
│ │ └── project-location-generate-body.test.ts
│ ├── run-runtime/
│ │ └── task-bridge.test.ts
│ ├── storage/
│ │ ├── bootstrap.test.ts
│ │ └── factory.test.ts
│ ├── task/
│ │ ├── async-poll-bailian.test.ts
│ │ ├── async-poll-external-id.test.ts
│ │ ├── async-poll-openai.test.ts
│ │ ├── error-catalog.contract.test.ts
│ │ ├── error-message.test.ts
│ │ ├── intent.test.ts
│ │ ├── llm-observe-contract.test.ts
│ │ ├── normalize-error.test.ts
│ │ ├── presentation.test.ts
│ │ ├── publisher.direct-run-events.test.ts
│ │ └── publisher.replay.test.ts
│ ├── user-api/
│ │ ├── llm-test-connection.test.ts
│ │ ├── model-llm-protocol-probe.test.ts
│ │ ├── model-template-save.test.ts
│ │ ├── model-template-schema.test.ts
│ │ ├── provider-test-compatible.test.ts
│ │ └── provider-test.test.ts
│ ├── voice/
│ │ ├── generate-voice-line.test.ts
│ │ └── provider-voice-binding.test.ts
│ ├── worker/
│ │ ├── analyze-global.test.ts
│ │ ├── analyze-novel.test.ts
│ │ ├── asset-hub-ai-design.test.ts
│ │ ├── asset-hub-ai-modify.test.ts
│ │ ├── asset-hub-image-suffix.test.ts
│ │ ├── character-image-task-handler.test.ts
│ │ ├── character-profile.test.ts
│ │ ├── clips-build.test.ts
│ │ ├── episode-split.test.ts
│ │ ├── image-task-handlers-core.test.ts
│ │ ├── image-worker.test.ts
│ │ ├── llm-proxy.test.ts
│ │ ├── llm-stream.test.ts
│ │ ├── location-image-task-handler.test.ts
│ │ ├── modify-image-reference-description.test.ts
│ │ ├── panel-image-task-handler.test.ts
│ │ ├── panel-variant-task-handler.test.ts
│ │ ├── reference-to-character.test.ts
│ │ ├── resolve-analysis-model.test.ts
│ │ ├── screenplay-convert.test.ts
│ │ ├── script-to-storyboard-atomic-retry.test.ts
│ │ ├── script-to-storyboard-orchestrator.retry.test.ts
│ │ ├── script-to-storyboard.test.ts
│ │ ├── shared.direct-run-events.test.ts
│ │ ├── shot-ai-prompt-appearance.test.ts
│ │ ├── shot-ai-prompt-location.test.ts
│ │ ├── shot-ai-prompt-shot.test.ts
│ │ ├── shot-ai-tasks.test.ts
│ │ ├── shot-ai-variants.test.ts
│ │ ├── story-to-script-orchestrator.retry.test.ts
│ │ ├── story-to-script.test.ts
│ │ ├── user-concurrency-gate.test.ts
│ │ ├── video-generation-resume.test.ts
│ │ ├── video-worker.test.ts
│ │ ├── voice-analyze.test.ts
│ │ ├── voice-design.test.ts
│ │ ├── voice-line-parse-helpers.test.ts
│ │ └── voice-worker.test.ts
│ └── workspace/
│ ├── episode-selection.test.ts
│ └── rebuild-confirm.test.ts
├── tsconfig.json
├── vitest.config.ts
└── vitest.core-coverage.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
# 版本控制
.git
.github
# 构建产物(builder 阶段重新生成)
.next
node_modules
# 测试
coverage
tests
vitest.config.ts
docker-compose.test.yml
# 运行时数据
logs
data
certificates
backups
uploads
# IDE 和 AI 工具
.vscode
.cursor
.claude
.gemini
.agent
.shared
.artifacts
# 数据库文件
*.db
*.db-journal
prisma/data
# 临时文件
.DS_Store
*.tsbuildinfo
.tmp-old-snapshot-*
# 环境变量
.env
.env.local
.env.*.local
.env.test
# 文档和杂项
*.md
*.py
debug-request.json
AGENTS.md
docs
agent
================================================
FILE: .eslintrc.json
================================================
{
"extends": "next/core-web-vitals",
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "warn",
"@next/next/no-img-element": "warn",
"react-hooks/exhaustive-deps": "warn"
}
}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# vercel
.vercel
# IDE and AI tools
.vscode/
.idea/
.claude/
.cursor/
.gemini/
.artifacts/
.agent/
.shared/
# typescript
*.tsbuildinfo
next-env.d.ts
/src/generated/prisma
# logs
/logs
*.log
# environment variables
.env
.env.local
.env.test
.env.*.local
docker-logs/
# database
*.db
*.db-journal
prisma/data/
# uploads (user data)
uploads/
/data/
certificates
# local temporary snapshots for old-version verification
.tmp-old-snapshot-*/
# temporary files
tmp/
# internal AI agent config (not for public)
AGENTS.md
agent/
# internal docs (not for public)
docs/
# GitHub CI workflows (internal)
# .github/ — 只保留 workflows,忽略其他
.github/*
!.github/workflows/
================================================
FILE: .husky/pre-commit
================================================
#!/usr/bin/env sh
set -e
npm run verify:commit
================================================
FILE: .husky/pre-push
================================================
#!/usr/bin/env sh
set -e
npm run verify:push
================================================
FILE: .nvmrc
================================================
22.14.0
================================================
FILE: .tmp_check_task.ts
================================================
import { prisma } from '@/lib/prisma'
const id = 'a3cbc6d3-8720-4584-addd-e2bc4ace7759'
async function main() {
const t = await prisma.task.findUnique({
where: { id },
select: {
id: true,
type: true,
userId: true,
projectId: true,
payload: true,
errorMessage: true,
createdAt: true,
},
})
console.log(JSON.stringify(t, null, 2))
if (!t) return
const pref = await prisma.userPreference.findUnique({
where: { userId: t.userId },
select: {
analysisModel: true,
customProviders: true,
customModels: true,
},
})
console.log('userPreference', JSON.stringify(pref, null, 2))
}
main()
.catch((err) => {
console.error(err)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})
================================================
FILE: CHANGELOG.md
================================================
# Changelog / 更新日志
All notable changes to this project will be documented in this file.
---
## [v0.2] - 2026-02-28
### ✨ 新功能
- 增加 OpenAI 兼容图片、视频格式支持
### 🐛 修复
- 修复默认模型配置后项目模型需要二次选择的问题
- 修复部分情况 resolution 无法读取的问题
- 修复模型链路为 LangGraph
- 修复默认参数无选择问题
- 修复关闭计费依然触发计费问题
- 修复 openai-compatible 被误判为原生 OpenAI 推理问题
- 修复 JSON 解析失败问题
### ⚙️ 优化
- 修改为默认计费 off
- 增强提示词 JSON 格式限制
---
## [v0.2.1] - 2026-02-28
### 🐛 修复
- 修复 AI 生成内容语言不跟随网站语言设置的问题
- 修复前端 API 请求未携带 Accept-Language header 导致 locale 回退到浏览器默认语言
---
## [v0.1] - 2026-02-27
### 🎉 首次发布
- 项目初始开源版本
================================================
FILE: Dockerfile
================================================
# ==================== Stage 1: Dependencies ====================
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
COPY prisma ./prisma
RUN npm ci
# ==================== Stage 2: Build ====================
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Prisma generate + Next.js build
RUN npm run build
# ==================== Stage 3: Production ====================
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# Install tini for proper signal handling
RUN apk add --no-cache tini
# node_modules(含 devDeps,因为 npm run start 需要 concurrently + tsx)
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
# Next.js 构建产物
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
# Prisma schema(db push 需要)
COPY --from=builder /app/prisma ./prisma
# Worker 和 Watchdog 源码(tsx 运行 TypeScript)
COPY --from=builder /app/src ./src
COPY --from=builder /app/scripts ./scripts
COPY --from=builder /app/lib ./lib
# 定价和配置标准
COPY --from=builder /app/standards ./standards
# 国际化 + 配置文件
COPY --from=builder /app/messages ./messages
COPY --from=builder /app/tsconfig.json ./tsconfig.json
COPY --from=builder /app/next.config.ts ./next.config.ts
COPY --from=builder /app/middleware.ts ./middleware.ts
COPY --from=builder /app/postcss.config.mjs ./postcss.config.mjs
# 运行日志目录 + 空 .env(tsx --env-file=.env 需要文件存在,实际 env 由 docker-compose 注入)
RUN mkdir -p /app/logs && touch /app/.env
EXPOSE 3000 3010
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["npm", "run", "start"]
================================================
FILE: README.md
================================================
<p align="center">
<a href="https://www.waoowaoo.com/">
<img src="images/cta-banner.png" alt="🚀 探索 AI 影视的下一代创作流 | 立即加入 waoowaoo 在线网页版内测候补" width="800">
</a>
</p>
<p align="center">
<img src="public/banner.png" alt="waoowaoo" width="600">
</p>
<h1 align="center">waoowaoo AI 影视 Studio</h1>
<p align="center">
一款基于 AI 技术的短剧/漫画视频制作工具,支持从小说文本自动生成分镜、角色、场景,并制作成完整视频。
</p>
<p align="center">
<a href="README_en.md">English</a> · <a href="https://www.waoowaoo.com/">加入内测候补</a> · <a href="https://github.com/saturndec/waoowaoo/issues">反馈问题</a>
</p>
> [!IMPORTANT]
> ⚠️ **测试版声明**:本项目目前处于测试初期阶段,由于暂时只有我一个人开发,存在部分 bug 和不完善之处。我们正在快速迭代更新中,**欢迎进群反馈问题和需求,及时关注项目更新!目前更新会非常频繁,后续会增加大量新功能以及优化效果,我们的目标是成为行业最强AI工具!**
<img src="images/dab6b4105e3260f37ba2d5f536dce259.jpg" width="30%">
---
## ✨ 功能特性
- 🎬 **AI 剧本分析** — 自动解析小说,提取角色、场景、剧情
- 🎨 **角色 & 场景生成** — AI 生成一致性人物和场景图片
- 📽️ **分镜视频制作** — 自动生成分镜头并合成视频
- 🎙️ **AI 配音** — 多角色语音合成
- 🌐 **多语言支持** — 中文 / 英文界面,右上角一键切换
---
## 🚀 快速开始
**前提条件**:安装 [Docker Desktop](https://docs.docker.com/get-docker/)
### 方式一:拉取预构建镜像(最简单)
无需克隆仓库,下载即用:
```bash
# 下载 docker-compose.yml
curl -O https://raw.githubusercontent.com/saturndec/waoowaoo/main/docker-compose.yml
# 启动所有服务
docker compose up -d
```
> ⚠️ 当前为测试版,版本间数据库不兼容。升级请先清除旧数据:
```bash
docker compose down -v
docker rmi ghcr.io/saturndec/waoowaoo:latest
curl -O https://raw.githubusercontent.com/saturndec/waoowaoo/main/docker-compose.yml
docker compose up -d
```
> 启动后请**清空浏览器缓存**并重新登录,避免旧版本缓存导致异常。
### 方式二:克隆仓库 + Docker 构建(完全控制)
```bash
git clone https://github.com/saturndec/waoowaoo.git
cd waoowaoo
docker compose up -d
```
更新版本:
```bash
git pull
docker compose down && docker compose up -d --build
```
### 方式三:本地开发模式(开发者)
```bash
git clone https://github.com/saturndec/waoowaoo.git
cd waoowaoo
# 复制环境变量配置文件(必须在 npm install 之前完成)
cp .env.example .env
# ⚠️ 编辑 .env,填入你的 AI API Key(NEXTAUTH_URL 默认已是 http://localhost:3000,无需修改)
npm install
# 只启动基础设施
# 注意:docker-compose.yml 将服务映射到非标准端口,.env.example 已按此预设
mysql:13306 redis:16379 minio:19000
docker compose up mysql redis minio -d
# 初始化数据库表结构(首次必须执行,跳过会导致启动后报错)
npx prisma db push
# 启动开发服务器
npm run dev
```
> [!WARNING]
> 跳过 `npx prisma db push` 会导致所有数据库表不存在,启动后报错 `The table 'tasks' does not exist`。请务必先运行此命令再启动开发服务器。
---
访问 [http://localhost:13000](http://localhost:13000)(方式一、二)或 [http://localhost:3000](http://localhost:3000)(方式三)开始使用!
> 首次启动会自动完成数据库初始化,无需任何额外配置。
> [!TIP]
> **如果遇到网页卡顿**:HTTP 模式下浏览器可能限制并发连接。可安装 [Caddy](https://caddyserver.com/docs/install) 启用 HTTPS:
> ```bash
> caddy run --config Caddyfile
> ```
> 然后访问 [https://localhost:1443](https://localhost:1443)
---
## 🔧 API 配置
启动后进入**设置中心**配置 AI 服务的 API Key,内置配置教程。
> 💡 **注意**:目前仅推荐使用各服务商官方 API,第三方兼容格式(OpenAI Compatible)尚不完善,后续版本会持续优化。
---
## 📦 技术栈
- **框架**: Next.js 15 + React 19
- **数据库**: MySQL + Prisma ORM
- **队列**: Redis + BullMQ
- **样式**: Tailwind CSS v4
- **认证**: NextAuth.js
---
## 📦 页面功能预览




---
## 🤝 参与方式
本项目由核心团队独立维护。欢迎你通过以下方式参与:
- 🐛 提交 [Issue](https://github.com/saturndec/waoowaoo/issues) 反馈 Bug
- 💡 提交 [Issue](https://github.com/saturndec/waoowaoo/issues) 提出功能建议
- 🔧 提交 Pull Request 供参考 — 我们会认真审阅每一个 PR 的思路,但最终由团队自行实现修复,不会直接合并外部 PR
---
**Made with ❤️ by waoowaoo team**
## Star History
[](https://www.star-history.com/#saturndec/waoowaoo&type=date&legend=top-left)
================================================
FILE: README_en.md
================================================
<p align="center">
<img src="public/banner.png" alt="waoowaoo" width="600">
</p>
<h1 align="center">waoowaoo AI Video Studio</h1>
<p align="center">
An AI-powered tool for creating short drama / comic videos — automatically generates storyboards, characters, and scenes from novel text, then assembles them into complete videos.
</p>
<p align="center">
<a href="README.md">中文文档</a> · <a href="https://www.waoowaoo.com/">Join Waitlist</a> · <a href="https://github.com/saturndec/waoowaoo/issues">Report Bug</a>
</p>
> [!IMPORTANT]
> **Beta Notice**: This project is currently in its early beta stage. As it is currently a solo-developed project, some bugs and imperfections are to be expected. We are iterating rapidly — please stay tuned for frequent updates! We are committed to rolling out a massive roadmap of new features and optimizations, with the ultimate goal of becoming the top-tier solution in the industry. Your feedback and feature requests are highly welcome!
---
## ✨ Features
- 🎬 **AI Script Analysis** — Parse novels, extract characters, scenes & plot automatically
- 🎨 **Character & Scene Generation** — Consistent AI-generated character and scene images
- 📽️ **Storyboard Video** — Auto-generate shots and compose into complete videos
- 🎙️ **AI Voiceover** — Multi-character voice synthesis
- 🌐 **Bilingual UI** — Chinese / English, switch in the top-right corner
---
## 🚀 Quick Start
**Prerequisites**: Install [Docker Desktop](https://docs.docker.com/get-docker/)
### Method 1: Pull Pre-built Image (Easiest)
No need to clone the repository. Just download and run:
```bash
# Download docker-compose.yml
curl -O https://raw.githubusercontent.com/saturndec/waoowaoo/main/docker-compose.yml
# Start all services
docker compose up -d
```
> ⚠️ This is a beta version. Database is not compatible between versions. To upgrade, clear old data first:
```bash
docker compose down -v
docker rmi ghcr.io/saturndec/waoowaoo:latest
curl -O https://raw.githubusercontent.com/saturndec/waoowaoo/main/docker-compose.yml
docker compose up -d
```
> After starting, please **clear your browser cache** and log in again to avoid issues caused by stale cache.
### Method 2: Clone & Docker Build (Full Control)
```bash
git clone https://github.com/saturndec/waoowaoo.git
cd waoowaoo
docker compose up -d
```
To update:
```bash
git pull
docker compose down && docker compose up -d --build
```
### Method 3: Local Development (For Developers)
```bash
git clone https://github.com/saturndec/waoowaoo.git
cd waoowaoo
# Copy environment config (must be done before npm install)
cp .env.example .env
# ⚠️ Edit .env to fill in your AI API Keys (NEXTAUTH_URL defaults to http://localhost:3000, no change needed)
npm install
# Start infrastructure only
docker compose up mysql redis minio -d
# Run database migration
npx prisma db push
# Start development server
npm run dev
```
---
Visit [http://localhost:13000](http://localhost:13000) (Method 1 & 2) or [http://localhost:3000](http://localhost:3000) (Method 3) to get started!
> The database is initialized automatically on first launch — no extra configuration needed.
> [!TIP]
> **If you experience lag**: HTTP mode may limit browser connections. Install [Caddy](https://caddyserver.com/docs/install) for HTTPS:
> ```bash
> caddy run --config Caddyfile
> ```
> Then visit [https://localhost:1443](https://localhost:1443)
---
## 🔧 API Configuration
After launching, go to **Settings** to configure your AI service API keys. A built-in guide is provided.
> 💡 **Note**: Currently only official provider APIs are recommended. Third-party compatible formats (OpenAI Compatible) are not yet fully supported and will be improved in future releases.
---
## 📦 Tech Stack
- **Framework**: Next.js 15 + React 19
- **Database**: MySQL + Prisma ORM
- **Queue**: Redis + BullMQ
- **Styling**: Tailwind CSS v4
- **Auth**: NextAuth.js
---
## 📦 Preview




---
## 🤝 Contributing
This project is maintained by the core team. You're welcome to contribute by:
- 🐛 Filing [Issues](https://github.com/saturndec/waoowaoo/issues) — report bugs
- 💡 Filing [Issues](https://github.com/saturndec/waoowaoo/issues) — propose features
- 🔧 Submitting Pull Requests as references — we review every PR carefully for ideas, but the team implements fixes internally rather than merging external PRs directly
---
**Made with ❤️ by waoowaoo team**
## Star History
[](https://www.star-history.com/#saturndec/waoowaoo&type=date&legend=top-left)
================================================
FILE: caddyfile
================================================
# HTTPS 反向代理(在主机上运行,非 Docker 内)
# 启动方式: caddy run --config Caddyfile
#
# 用法:
# 1. docker compose up -d (启动 App + MySQL + Redis)
# 2. caddy run --config Caddyfile (启动 HTTPS 代理)
# 3. 打开 https://localhost:4443 或 https://your-ip:4443
#
# 修改下方 IP 为你的局域网 IP(ifconfig en0 查看)
# 例如: localhost:1443, https://192.168.x.x:1443 {
localhost:1443 {
handle /admin/queues* {
reverse_proxy localhost:13010
}
handle {
reverse_proxy localhost:13000
}
tls internal
}
================================================
FILE: debug-request.json
================================================
{
"model": "doubao-seedream-4-0-250828",
"prompt": "Lily and Olivia in 医院病房_日夜, medium shot, dramatic lighting, American comic style",
"sequential_image_generation": "disabled",
"response_format": "url",
"size": "1080x1920",
"stream": false,
"watermark": false
}
================================================
FILE: docker-compose.test.yml
================================================
services:
mysql:
image: mysql:8.0
container_name: waoowaoo-test-mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: waoowaoo_test
MYSQL_ROOT_HOST: "%"
ports:
- "3307:3306"
command:
- "--default-authentication-plugin=mysql_native_password"
- "--sql_mode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -proot"]
interval: 5s
timeout: 5s
retries: 30
start_period: 15s
redis:
image: redis:7-alpine
container_name: waoowaoo-test-redis
ports:
- "6380:6379"
command: ["redis-server", "--appendonly", "no"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 30
start_period: 5s
================================================
FILE: docker-compose.yml
================================================
services:
# ==================== MySQL ====================
mysql:
image: mysql:8.0
container_name: waoowaoo-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: waoowaoo123
MYSQL_DATABASE: waoowaoo
MYSQL_ROOT_HOST: "%"
ports:
- "13306:3306"
volumes:
- mysql_data:/var/lib/mysql
command:
- "--default-authentication-plugin=mysql_native_password"
- "--sql_mode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -pwaoowaoo123"]
interval: 5s
timeout: 5s
retries: 30
start_period: 15s
# ==================== Redis ====================
redis:
image: redis:7-alpine
container_name: waoowaoo-redis
restart: unless-stopped
ports:
- "16379:6379"
volumes:
- redis_data:/data
command: ["redis-server", "--appendonly", "yes"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 30
start_period: 5s
# ==================== MinIO ====================
minio:
image: minio/minio:RELEASE.2025-02-28T09-55-16Z
container_name: waoowaoo-minio
restart: unless-stopped
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
command: server /data --console-address ":9001"
ports:
- "19000:9000"
- "19001:9001"
volumes:
- minio_data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:9000/minio/health/live"]
interval: 5s
timeout: 5s
retries: 30
start_period: 10s
# ==================== App (Next.js + Workers) ====================
app:
image: ghcr.io/saturndec/waoowaoo:latest
container_name: waoowaoo-app
restart: unless-stopped
environment:
# 数据库(指向容器内部 MySQL,用服务名 mysql 而非 localhost)
DATABASE_URL: "mysql://root:waoowaoo123@mysql:3306/waoowaoo"
# Redis(指向容器内部 Redis,用服务名 redis)
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_USERNAME: ""
REDIS_PASSWORD: ""
REDIS_TLS: ""
# 存储:默认 MinIO(S3 兼容)
STORAGE_TYPE: minio
MINIO_ENDPOINT: "http://minio:9000"
MINIO_REGION: "us-east-1"
MINIO_BUCKET: "waoowaoo"
MINIO_ACCESS_KEY: "minioadmin"
MINIO_SECRET_KEY: "minioadmin"
MINIO_FORCE_PATH_STYLE: "true"
# 外部访问地址(浏览器实际访问)
NEXTAUTH_URL: "http://localhost:13000"
# 容器内自调用地址(服务端 fetch 自己的 API / 文件)
INTERNAL_APP_URL: "http://127.0.0.1:3000"
NEXTAUTH_SECRET: "waoowaoo-default-secret-2026"
# 内部密钥
CRON_SECRET: "waoowaoo-docker-cron-secret"
INTERNAL_TASK_TOKEN: "waoowaoo-docker-task-token"
API_ENCRYPTION_KEY: "waoowaoo-opensource-fixed-key-2026"
# Worker 配置
WATCHDOG_INTERVAL_MS: "30000"
TASK_HEARTBEAT_TIMEOUT_MS: "90000"
QUEUE_CONCURRENCY_IMAGE: "50"
QUEUE_CONCURRENCY_VIDEO: "50"
QUEUE_CONCURRENCY_VOICE: "20"
QUEUE_CONCURRENCY_TEXT: "50"
# Bull Board
BULL_BOARD_HOST: "0.0.0.0"
BULL_BOARD_PORT: "3010"
BULL_BOARD_BASE_PATH: "/admin/queues"
BULL_BOARD_USER: ""
BULL_BOARD_PASSWORD: ""
# 日志
LOG_UNIFIED_ENABLED: "true"
LOG_LEVEL: "INFO"
LOG_FORMAT: "json"
LOG_DEBUG_ENABLED: "false"
LOG_AUDIT_ENABLED: "true"
LOG_SERVICE: "waoowaoo"
LOG_REDACT_KEYS: "password,token,apiKey,apikey,authorization,cookie,secret,access_token,refresh_token"
# 计费
BILLING_MODE: "OFF"
# 流式
LLM_STREAM_EPHEMERAL_ENABLED: "true"
ports:
- "13000:3000"
- "13010:3010"
volumes:
- ./data:/app/data
- ./docker-logs:/app/logs
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
minio:
condition: service_healthy
command: >
sh -c "
npx prisma db push --skip-generate &&
(sleep 5 && echo '' &&
echo '╔══════════════════════════════════════════════════╗' &&
echo '║ waoowaoo is ready! ║' &&
echo '║ ║' &&
echo '║ HTTP: http://localhost:13000 ║' &&
echo '║ ║' &&
echo '║ For HTTPS, run Caddy on host: ║' &&
echo '║ caddy run --config Caddyfile ║' &&
echo '║ Then open: https://localhost:1443 ║' &&
echo '╚══════════════════════════════════════════════════╝' &&
echo '') &
npm run start
"
volumes:
mysql_data:
redis_data:
minio_data:
================================================
FILE: eslint.config.mjs
================================================
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
{
ignores: [
"node_modules/**",
".agent/**",
".next/**",
"out/**",
"build/**",
"coverage/**",
"next-env.d.ts",
],
},
{
files: ["src/**/*.{ts,tsx}"],
ignores: ["src/components/ui/icons/**"],
rules: {
"no-restricted-imports": [
"error",
{
paths: [
{
name: "lucide-react",
message: "Import icons through '@/components/ui/icons' only.",
},
],
},
],
"no-restricted-syntax": [
"error",
{
selector: "JSXOpeningElement[name.name='svg']",
message:
"Use AppIcon or icons module components instead of inline <svg>.",
},
],
},
},
];
export default eslintConfig;
================================================
FILE: extract_chinese.py
================================================
#!/usr/bin/env python3
"""
提取React/TypeScript代码中的硬编码中文字符串
"""
import re
import os
from pathlib import Path
import json
def extract_chinese_strings(file_path):
"""提取文件中的中文字符串"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
except:
return []
results = []
# 匹配JSX/TSX中的中文字符串
# 1. {' 中文 '} 或 {"中文"}
pattern1 = r'\{\s*[\'"]([^\'"\{\}]*[\u4e00-\u9fff]+[^\'"\{\}]*)[\'\"]\s*\}'
# 2. >中文<
pattern2 = r'\>([^<\>]*[\u4e00-\u9fff]+[^<\>]*)\<'
# 3. placeholder="中文" 等属性
pattern3 = r'(?:placeholder|title|alt|value|defaultValue|confirmText|cancelText|message)\s*=\s*[\'"]([^\'\"]*[\u4e00-\u9fff]+[^\'\"]*)[\'"]'
# 4. 字符串默认值 = '中文'
pattern4 = r'=\s*[\'"]([^\'\"]*[\u4e00-\u9fff]+[^\'\"]*)[\'"]'
for pattern in [pattern1, pattern2, pattern3, pattern4]:
matches = re.finditer(pattern, content)
for match in matches:
chinese_text = match.group(1).strip()
if chinese_text and len(chinese_text) > 0:
# 跳过注释
line_num = content[:match.start()].count('\n') + 1
line = content.split('\n')[line_num - 1]
if '//' in line and line.index('//') < line.find(chinese_text):
continue
results.append({
'text': chinese_text,
'line': line_num,
'category': 'unknown'
})
# 去重
seen = set()
unique_results = []
for r in results:
key = f"{r['text']}_{r['line']}"
if key not in seen:
seen.add(key)
unique_results.append(r)
return unique_results
def scan_directory(base_path,exclude_patterns=['test-ui']):
"""扫描目录中的所有TSX/TS文件"""
all_findings = {}
for root, dirs, files in os.walk(base_path):
# 排除特定目录
dirs[:] = [d for d in dirs if d not in exclude_patterns and not d.startswith('.')]
for file in files:
if file.endswith(('.tsx', '.ts')):
file_path = os.path.join(root, file)
relative_path = os.path.relpath(file_path, base_path)
findings = extract_chinese_strings(file_path)
if findings:
all_findings[relative_path] = findings
return all_findings
if __name__ == '__main__':
base_dir = 'src'
results = scan_directory(base_dir)
# 输出结果
total = 0
for file_path, findings in sorted(results.items()):
if findings:
print(f"\n## {file_path} ({len(findings)} strings)")
for finding in findings[:10]: # 只显示前10个
print(f" Line {finding['line']}: {finding['text'][:60]}")
total += len(findings)
if len(findings) > 10:
print(f" ... and {len(findings) - 10} more")
print(f"\n\n总计: {len(results)} 个文件, {total} 处硬编码中文")
================================================
FILE: lib/prompts/character-reference/character_image_to_description.en.txt
================================================
# Character Image To Description Prompt
Analyze the provided character image and write one detailed English visual description for image generation.
## Required content
Include all of the following:
1. Gender and approximate age range
2. Hair style and hair color
3. Face shape and facial features
4. Body build and silhouette
5. Clothing style and clothing details
6. Accessories and signature details
7. Overall style keywords
## Missing-part completion
If the image only shows part of the body, infer the missing parts consistently.
- Missing lower body: infer matching pants/skirt style
- Missing shoes: infer shoes that fit the outfit
- Missing accessory details: infer a few reasonable accessories
## Forbidden content
Do not mention:
- Skin color
- Eye color
- Facial expression
- Pose or action
- Background
## Output format
Return one plain English paragraph only (about 120-220 words).
Do not return markdown, bullets, titles, or JSON.
================================================
FILE: lib/prompts/character-reference/character_image_to_description.zh.txt
================================================
# 图片反推角色描述提示词
请分析这张角色图片,生成一段详细的角色外貌描述(用于 AI 图片生成)。
## 输出要求
生成一段完整的角色视觉描述,包含以下要素:
1. 性别和年龄段(如:约二十五岁的男性)
2. 发型发色(如:黑色短发、微卷的棕色长发)
3. 脸型五官特征(如:剑眉星目、高鼻梁、薄唇)
4. 体态身材(如:身形修长、体格健壮)
5. 服装风格(如:深蓝色西装、白色衬衫、皮质腰带)
6. 配饰特征(如:左手戴银色手表、胸前别金色胸针)
7. 整体气质关键词(如:精英气质、禁欲系、高冷、温柔暖男)
## 缺失内容补齐规则
如果参考图只展示了部分身体(如上半身、头像),请根据已有信息合理推断并补全:
- **缺少下半身**:根据上衣风格推断裤装/裙装类型(如西装上衣配深蓝色西裤、休闲上衣配牛仔裤)
- **缺少鞋子**:根据整体穿搭风格推断鞋款(如正装配皮鞋、休闲装配运动鞋或帆布鞋)
- **缺少配饰细节**:根据角色气质合理添加配饰(如商务风配手表、休闲风配手环)
## 禁止描写
- 皮肤颜色
- 眼睛颜色
- 表情
- 动作
- 背景
- 姿势
## 输出格式
一段连贯的描述文字,约200-300字,直接可用于图片生成提示词。
只返回描述文字,不要有任何标题、序号或其他格式。
================================================
FILE: lib/prompts/character-reference/character_reference_to_sheet.en.txt
================================================
# Reference Image To Character Sheet Prompt (img2img)
Use the provided reference image to extract face traits, hairstyle, body shape, and outfit structure.
## Style priority
Follow the user-selected style instruction as the highest-priority rule.
Use reference images only to preserve character identity traits (face, hairstyle, body shape, outfit structure), and do not let reference style override the requested style.
Only if no explicit style instruction is provided, you may preserve the original visual style.
## Generation rules
1. Ignore the original image color cast and lighting defects
2. Use clean, soft studio lighting
3. Keep natural, aesthetically correct body proportions
4. Do not copy blur, noise, compression artifacts, or defects
5. Output must be clear, sharp, and production-quality
6. Character expression should be neutral and calm, looking at camera
## Missing-part completion
If only half body or partial body is visible, infer and complete hidden parts consistently:
- Infer matching lower-body clothing
- Infer suitable footwear
- Infer missing hands/arms in a natural way
================================================
FILE: lib/prompts/character-reference/character_reference_to_sheet.zh.txt
================================================
# 参考图转角色设定图提示词(图生图模式)
基于提供的参考图片,提取角色的面部五官特征、发型、体型和服装款式作为参考。
## 画风优先级规则
画风由用户选择的风格指令决定,严格遵循风格指令进行生成。
参考图仅用于保持角色身份特征(五官、发型、体型、服装结构),不能覆盖用户指定画风。
仅在未提供风格指令时,才可参考原图画风。
## 生成规则
1. 忽略原图的具体色调和光线
2. 使用自然柔和的摄影棚灯光
3. 绘制正常美观的人体比例
4. 不要复制原图的画质、模糊、噪点或瑕疵
5. 生成的图像必须清晰锐利、细节丰富、专业品质
6. 角色表情应为自然平静的中性表情,目光正视镜头
## 缺失部位自动补齐
如果参考图是半身或部分身体,请根据服装风格和人物特征合理补全未露出的部位:
- **缺少下半身**:根据上衣风格推断并绘制匹配的裤装/裙装
- **缺少脚部**:根据整体穿搭风格添加合适的鞋款
- **缺少手部/手臂**:根据姿态合理补全
- 保持整体风格一致,确保补全的部分与可见部分协调统一
================================================
FILE: lib/prompts/novel-promotion/agent_acting_direction.en.txt
================================================
You are an experienced Acting Director.
Your task is to generate acting notes for each character in each panel.
Core input:
- Total panel count: {panel_count}
- Panels JSON:
{panels_json}
- Character info:
{characters_info}
Requirements:
1. Treat each panel independently. The same character can have different emotional states across panels.
2. Adapt performance style to panel.scene_type (daily / emotion / action / epic / suspense).
3. For each character, write one concise visual acting instruction including:
- emotional state (visible, not abstract)
- facial expression details
- body language / posture
- micro action and gaze direction
4. Use only observable descriptions. Avoid abstract words like "sad" without visual evidence.
Output format (JSON array only):
[
{
"panel_number": 1,
"characters": [
{
"name": "Character Name",
"acting": "One-sentence visual acting direction"
}
]
}
]
Strict constraints:
1. Return JSON only, no markdown.
2. Array length must equal {panel_count}.
3. Each character object must contain only "name" and "acting".
4. Keep character names exactly consistent with panel input.
5. ⚠️ JSON SAFETY: All quotation marks in text (""''「」 etc.) MUST be converted to corner brackets「」in JSON string values. NEVER use raw ASCII double quotes " inside string values.
================================================
FILE: lib/prompts/novel-promotion/agent_acting_direction.zh.txt
================================================
你是一位经验丰富的表演指导(Acting Director)。你的任务是为一组分镜中的**每个镜头**设计角色的表演细节。
【核心职责】
分析整组分镜后,为每个镜头中的角色用一句话描述完整的表演指令,包含:
- 情绪状态与强度
- 面部表情细节
- 肢体语言与姿态
- 微动作与视线
【重要】每个镜头的表演必须是独立的!
- 同一角色在不同镜头可能有不同情绪变化
- 表演风格匹配 scene_type(日常/情感/动作/史诗/悬疑)
【表演风格匹配 scene_type】
**daily(日常)**:自然松弛,微表情为主,动作幅度小
**emotion(情感)**:细腻层次,眼神戏份重,情绪渐进
**action(动作)**:爆发力强,动作干脆,表情夸张
**epic(史诗)**:庄重仪式感,姿态端正,动作缓慢有力
**suspense(悬疑)**:紧绷警觉,肢体僵硬,眼神游移
【表演描述词库】
**表情**:眼眶泛红、眉头紧锁、嘴角上扬、目光闪躲、瞳孔收缩、嘴唇颤抖、咬紧牙关
**肢体**:握紧拳头、身体前倾、双手交握、肩膀耸起、转身背对、后退一步
**微动作**:轻轻眨眼、咽口水、深呼吸、手指轻颤、舔嘴唇、胸口起伏
【⚠️ 禁止规则】
1. 禁止抽象情绪词:悲伤、愤怒、紧张 → 改用可见表现
2. 禁止身份称呼:母亲、父亲 → 改用角色名
【输出格式】
返回JSON数组,每个镜头一个对象,每个角色只有 name + acting 两个字段:
[
{
"panel_number": 1,
"characters": [
{
"name": "李凤华",
"acting": "嘴角微扬眼神柔和地看向景笙,身体微微前倾,双手自然垂放,轻轻眨眼"
},
{
"name": "景笙",
"acting": "面带微笑但眼神略显疏离,站姿笔直双手背后,轻轻点头"
}
]
},
{
"panel_number": 2,
"characters": [
{
"name": "李凤华",
"acting": "眉头轻皱嘴唇紧抿,双手交握在身前肩膀微耸,目光低垂看向地面,手指轻微交缠"
}
]
},
{
"panel_number": 3,
"characters": [
{
"name": "李凤华",
"acting": "眼眶泛红泪水打转嘴唇颤抖,身体微微发抖双手攥紧衣角,快速眨眼忍住泪水转头避开对方视线"
}
]
}
]
【输入数据】
分镜数据(共 {panel_count} 个镜头):
{panels_json}
角色信息:
{characters_info}
【严格要求】
1. 只返回JSON数组,不要有markdown代码块标记
2. 数组长度必须等于输入的镜头数量({panel_count}个)
3. 每个角色只有 name 和 acting 两个字段
4. acting 用一句话描述完整表演(表情+肢体+微动作+视线)
5. 角色名必须与输入分镜中的 characters 完全一致
6. 所有描述必须是可视化的,禁止抽象情绪词
7. 根据 scene_type 调整表演风格
8. 情绪弧线连贯,前后镜头有合理递进
9. ⚠️ JSON安全:所有引号(""''等)在 JSON 字符串值中必须统一替换为「」,严禁出现未转义的英文双引号 "
================================================
FILE: lib/prompts/novel-promotion/agent_character_profile.en.txt
================================================
You are a casting and character-asset analyst.
Analyze the input text and produce structured character profiles for visual production.
Input text:
{input}
Existing character library info:
{characters_lib_info}
Goals:
1. Identify characters that should appear visually.
2. Exclude pure background extras and abstract entities.
3. Build profile fields needed for downstream visual generation.
4. Capture naming/alias mapping, especially first-person references.
Extraction rules:
1. Include characters that speak, act, or significantly drive plot.
2. Exclude one-off nameless background people unless visual identity is required.
3. Resolve aliases/titles (e.g., "my husband", "boss", "I") to canonical names when possible.
4. For first-person narrative, explicitly document who "I" maps to in introduction.
Field rules:
- role_level: one of S/A/B/C/D based on narrative importance
- costume_tier: 1-5 based on social identity (not role_level)
- expected_appearances: always include at least one initial appearance
- introduction: include identity, relationship mapping, and common address mapping
Output format (JSON only):
{
"characters": [
{
"name": "Canonical Name",
"aliases": ["alias 1", "alias 2"],
"introduction": "Role, perspective mapping, relationships, and naming aliases",
"gender": "male/female/other",
"age_range": "young adult",
"role_level": "S",
"archetype": "character archetype",
"personality_tags": ["tag1", "tag2"],
"era_period": "modern/fantasy/historical/sci-fi",
"social_class": "elite/middle/common",
"occupation": "occupation or none",
"costume_tier": 3,
"suggested_colors": ["color1", "color2"],
"primary_identifier": "signature visual marker",
"visual_keywords": ["keyword1", "keyword2"],
"expected_appearances": [
{ "id": 1, "change_reason": "initial appearance" }
]
}
],
"new_characters": [
{
"name": "Canonical Name",
"aliases": ["alias 1", "alias 2"],
"introduction": "Role, perspective mapping, relationships, and naming aliases",
"gender": "male/female/other",
"age_range": "young adult",
"role_level": "S",
"archetype": "character archetype",
"personality_tags": ["tag1", "tag2"],
"era_period": "modern/fantasy/historical/sci-fi",
"social_class": "elite/middle/common",
"occupation": "occupation or none",
"costume_tier": 3,
"suggested_colors": ["color1", "color2"],
"primary_identifier": "signature visual marker",
"visual_keywords": ["keyword1", "keyword2"],
"expected_appearances": [
{ "id": 1, "change_reason": "initial appearance" }
]
}
],
"updated_characters": [
{
"name": "Existing Canonical Name",
"updated_introduction": "Updated intro with newly discovered mapping",
"updated_aliases": ["new alias 1", "new alias 2"]
}
]
}
Strict constraints:
1. JSON only.
2. If no valid character is found, return empty arrays.
3. Keep all strings in English.
4. ⚠️ JSON SAFETY: All quotation marks in text (""''「」 etc.) MUST be converted to corner brackets「」in JSON string values. NEVER use raw ASCII double quotes " inside string values.
================================================
FILE: lib/prompts/novel-promotion/agent_character_profile.zh.txt
================================================
你是专业的"选角指导"。请基于提供的文本(小说、剧本或混合格式),分析并输出所有需要制作形象的角色档案信息。
【你的职责】
- 识别需要在画面中出现的角色
- 根据剧情发展和角色身份判断每个角色的重要性层级
- 分析角色的性格和背景
- 输出结构化的角色档案(供后续视觉生成使用)
- ⚠️ 分析角色之间的关系、称呼映射,生成角色介绍(introduction)
【筛选规则 - 精准提取模式】
✅【必须提取的角色】:
- 剧本人物行中列出的角色
- 有台词且参与剧情互动的角色
- 贯穿故事主线的核心人物
- 对剧情有实际推动作用的配角
- 在画面中需要出镜的角色
❌【不提取的角色】:
- 无名无特征的纯路人(如"人群中的某人")
- 仅被提及但从未出场的角色
- 没有台词也没有互动的背景人物
- 意境描述中的虚构存在(如"命运"、"死神的化身")
📋【判断标准】:
问自己:这个角色是否需要制作形象图?是否在画面中有实际出镜?
如果答案是否定的,则不提取。
【角色介绍 introduction 规则 ⭐重要】
每个角色必须有 introduction 字段,用于帮助后续 AI 正确识别角色。包含:
1. **叙述视角映射**:
- 如果是第一人称叙述,明确说明"我"对应此角色
- 示例:"本角色是故事主角,小说以第一人称'我'叙述"
2. **角色身份定位**:
- 描述角色在故事中的身份(主角/配角/反派等)
- 示例:"女主角,公司秘书"
3. **角色关系**:
- 与其他主要角色的关系
- 示例:"林墨的妻子,张三的女儿"
4. **称呼映射**:
- 其他角色对此角色的常用称呼
- 示例:"被林墨称呼为'老婆'、'晴晴',被张三称呼为'闺女'"
示例 introduction:
"故事主角,小说以第一人称'我'叙述,真名林墨。苏晴的丈夫,张三的女婿。被苏晴称呼为'老公'、'墨哥',被下属称呼为'林总'。"
【角色重要性层级判断规则】
⚠️ 重要:根据角色在剧情中的戏份和身份地位来判断,不是根据外表华丽程度!
S级(绝对主角):
- 故事的核心视角人物,剧情围绕其展开
- 第一人称叙述中的"我"通常是S级
- 判断依据:戏份最重、出场最多、剧情主线与其紧密相关
A级(核心配角):
- 与主角有大量互动的重要角色
- 男二号、女二号、主要反派等
- 判断依据:对主线剧情有重大影响、戏份仅次于主角
B级(重要配角):
- 多次出场、有名有姓、推动某条支线剧情
- 判断依据:有一定戏份、对剧情有贡献
C级(次要角色):
- 偶尔出场、戏份较少但有具体形象
- 判断依据:需要出镜但戏份不多
D级(群众演员):
- 有短暂出镜需求的小角色
- 判断依据:仅在个别场景出现
【服装华丽度层级 costume_tier】
⚠️ 服装华丽度由角色的社会身份和剧情设定决定,不是由重要性决定!
- 主角可以是朴素穿着(如穷学生主角=tier 2)
- 配角可以是华丽服装(如富家公子配角=tier 5)
5级(皇室/顶奢级):皇室成员、顶级富豪,服装极致华丽,有精美的刺绣、镶嵌或定制剪裁。
4级(贵族/精英级):贵族、企业家,服装精致考究,使用高档面料和精致细节。
3级(专业/品质级):中产阶级、专业人士,服装得体有品,剪裁讲究。
2级(日常/普通级):普通人、学生,服装简洁日常,款式普通但整洁。
1级(朴素/统一级):平民、底层劳动者,服装朴素统一,基础款式,功能性为主。
【角色原型 archetype 参考词库】
正派角色可以选择:霸道总裁、高冷学霸、温柔暖男、励志少年、贤惠女主、独立女强人、忠诚护卫等。
反派角色可以选择:心机婊、白莲花、阴险反派、疯批美人、复仇者等。
其他类型:傲娇公主、病娇、腹黑、毒舌、话痨、冷面热心、闷骚等。
【性格标签 personality_tags 参考词库】
气质类标签:高冷、温柔、阳光、忧郁、神秘、妩媚、清冷、热情
性格类标签:腹黑、傲娇、毒舌、话痨、闷骚、直爽、圆滑、固执
态度类标签:自信、自卑、孤僻、合群、叛逆、顺从
【视觉关键词 visual_keywords 参考词库】
风格类关键词:精英气质、街头潮流、学院风、复古优雅、运动活力、文艺气息、冷淡极简
特征类关键词:病弱感、禁欲系、狼狗系、奶狗系、御姐范、萝莉感、大叔味
【色彩建议规则】
根据角色类型选择合适的色彩:
正派主角适合白色、蓝色、金色或浅色系,传达正义和光明感。
反派角色适合黑色、暗红、深紫或暗色系,营造神秘或压迫感。
温柔角色适合米白、淡粉、浅绿等柔和色,体现温暖亲和。
冷酷角色适合黑色、灰色、深蓝等冷色调,强调距离感。
活泼角色适合橙色、黄色等亮色系,展现活力和热情。
【辨识标志设计规则】
为S级和A级角色设计一眼就能认出的标志性特征:
面部标志:眼角泪痣、剑眉、刀疤、胎记等独特面部特征。
发型标志:白发、挑染、独特发型、发带等醒目的头发特征。
服装标志:永远穿红色、标志性围巾、招牌外套等固定的服装元素。
配饰标志:家传戒指、从不摘下的项链、拐杖等标志性物品。
【子形象筛选规则 - 识别视觉外观变化 ⭐重要】
分析原文中角色是否有多个视觉形态,输出到 expected_appearances 字段。
✅ 需要记录的子形象(视觉上可见的变化):
- 衣着变化:换装、更换正装/休闲装、穿戴盔甲等
- 年龄变化:穿越、回忆场景中的年轻/年老状态
- 特殊装扮:出浴(围浴巾)、冒充他人的装扮
- 发型改变:剪头、编发、盘发、披发等持续性外观变化
❌ 不需要记录的(非视觉或临时状态):
- 情绪/心理状态:生气、开心、难过、紧张
- 健康状态:生病、发烧(除非有明显视觉特征如绷带)
- 临时动作:跑步、跳跃、战斗姿势
- 模糊描述:"蒙上了一层阴影""眼神变了"等抽象描述
- 临时特效/光影状态:散发光芒、身上发光、气场外放、浑身火焰、佛光环绕、金光闪闪等后期可添加的特效
- 战斗技能释放:发功、运功、施法、放大招、释放法术等技能状态
- 一次性瞬间状态:被打飞、摔倒、中招、受击等不持续的状态
⚠️ 判断标准:
- 如果一个状态无法通过换装来体现,就不需要记录
- 如果一个状态是通过后期特效(如发光、粒子、光环、火焰等)来表现的,不需要记录
- 如果一个状态只持续几秒而非整个场景,不需要记录
- 只有持续性的、需要重新制作人物形象图的外观变化才需要记录
📋 expected_appearances 格式:
- 每个角色必须至少有一个 id=1 的"初始形象"
- 如有换装/年龄变化等,添加 id=2, 3... 的子形象
- change_reason 简要说明变化原因(如"出浴状态"、"战斗装束"、"年老回忆")
【已有资产库】
⚠️ 重要:请仔细阅读已有角色的介绍,判断新发现的角色名是否与已有角色是同一人!
{characters_lib_info}
【输出格式 - 支持新增和更新】
只返回JSON,禁止任何markdown标记或注释。
输出包含两个数组:
- new_characters: 新发现的角色
- updated_characters: 需要更新介绍的已有角色(如发现了新的称呼、关系、或真名)
{
"new_characters": [
{
"name": "角色名",
"aliases": ["别名1", "别名2"],
"introduction": "角色介绍:身份定位、叙述视角映射、与其他角色的关系、常用称呼",
"gender": "男/女",
"age_range": "约二十五岁",
"role_level": "S/A/B/C/D",
"archetype": "角色原型(如霸道总裁)",
"personality_tags": ["高冷", "腹黑"],
"era_period": "现代都市/古代唐朝/未来科幻",
"social_class": "上层精英/中产/平民",
"occupation": "企业家/学生/无",
"costume_tier": 5,
"suggested_colors": ["深蓝", "金色"],
"primary_identifier": "眼角泪痣(仅S/A级需要)",
"visual_keywords": ["精英气质", "禁欲系"],
"expected_appearances": [
{"id": 1, "change_reason": "初始形象"},
{"id": 2, "change_reason": "换装/特殊状态的原因(如有)"}
]
}
],
"updated_characters": [
{
"name": "已有角色名(必须与资产库中的名字完全一致)",
"updated_introduction": "更新后的角色介绍(补充新发现的关系、称呼、真名等)",
"updated_aliases": ["新发现的别名1", "新发现的别名2"]
}
]
}
【更新规则】
⚠️ 什么情况下应该更新已有角色(放入 updated_characters):
1. **发现真名**:之前只有"我",现在发现"我"的真名是"林墨"
→ 更新 introduction 说明映射,添加 updated_aliases: ["林墨"]
2. **发现新称呼**:之前不知道别人怎么称呼这个角色,现在发现有人叫他"林总"
→ 更新 introduction 添加称呼信息,添加 updated_aliases: ["林总"]
3. **发现新关系**:之前不知道角色间的关系,现在发现苏晴是林墨的妻子
→ 更新双方的 introduction 添加关系信息
4. **不要重复创建**:如果发现"林墨"其实就是已有的"我",不要创建新角色,而是更新"我"的介绍和别名
【严格要求】
1. 只返回JSON,不得有其他文字
2. role_level 必须是 S/A/B/C/D 之一
3. costume_tier 必须是 1-5 的整数
4. S/A 级角色必须有 primary_identifier
5. personality_tags 至少2个,最多5个
6. suggested_colors 2-3个颜色
7. introduction 必填,描述角色身份、关系、称呼映射
8. 如果发现已有角色的新信息,放入 updated_characters 而不是创建新角色
9. updated_characters 中的 name 必须与已有资产库中的名字完全一致
10. expected_appearances 必填,至少包含 id=1 的初始形象
11. 只有持续性视觉变化才添加子形象,临时特效/情绪/动作不添加
12. 输出必须是**严格合法的JSON**:字符串中不能出现原始换行/回车/制表符
⚠️⚠️⚠️【JSON安全输出 - 最高优先级】⚠️⚠️⚠️
- 原文中的所有引号(""''「」『』等)在 JSON 字符串值中必须统一替换为日式方括号引号「」
- ❌ 严禁在 JSON 字符串值中出现英文双引号 " !会破坏 JSON 结构!
- ✅ 正确:"introduction":"他被称为「弼马温」"
- ❌ 错误:"introduction":"他被称为"弼马温"" ← 内部裸引号破坏JSON
- 如果字符串内确实需要英文双引号,必须用 \" 转义
【原文内容】
{input}
================================================
FILE: lib/prompts/novel-promotion/agent_character_visual.en.txt
================================================
You are a character visual designer.
Generate image-ready appearance descriptions from character profiles.
Character profiles JSON:
{character_profiles}
Rules:
1. Keep identity consistency with profile fields.
2. Convert personality/social identity into visual details (face, hair, outfit, accessories).
3. Support both human and non-human characters.
4. Respect era_period and costume_tier.
5. If primary_identifier exists, include it clearly in each main description.
6. Do not include expression, action, background, or plot narration.
7. Do not include skin color, eye color, or lip color.
Appearance strategy:
- Initial appearance should be complete and self-contained.
- Additional appearances should focus on visual changes indicated by change_reason.
- Provide 3 alternative description lines per appearance.
Output format (JSON only):
{
"characters": [
{
"name": "Character Name",
"appearances": [
{
"id": 0,
"change_reason": "initial appearance",
"descriptions": [
"description variant 1",
"description variant 2",
"description variant 3"
]
}
]
}
]
}
Strict constraints:
1. JSON only.
2. Keep names exactly aligned with input profiles.
3. Each descriptions array must contain at least 3 valid English strings.
4. ⚠️ JSON SAFETY: All quotation marks in text (""''「」 etc.) MUST be converted to corner brackets「」in JSON string values. NEVER use raw ASCII double quotes " inside string values.
================================================
FILE: lib/prompts/novel-promotion/agent_character_visual.zh.txt
================================================
你是专业的"角色视觉设计师"。根据角色档案信息,生成详细的人物外貌描述(用于AI图片生成)。
【你的职责】
- 根据角色档案生成对应的外貌描述
- 确保核心角色有明显的视觉辨识度
- 体现角色性格和身份的视觉特征
- 服装华丽度由角色身份决定,与重要性无关
【角色类型灵活处理规则】
⚠️ 角色不一定是人类!请根据原文判断角色的实际形态:
**人类角色**:按照下方的面部、发型、体态、服装规范描述
**非人类角色**(动物、神话生物、知名形象等):
- 描述开头必须以角色名/物种名开始
- 根据角色实际形态自由描述外观特征,不受人类模板限制
- 保持角色的核心辨识特征
示例:
- 孙悟空 → "孙悟空,身穿虎皮裙,头戴紧箍咒金环,手持如意金箍棒,毛发金黄蓬松,尖耳竖立,眼神机灵狡黠..."
- 蜗牛 → "蜗牛,背负螺旋形褐色硬壳,壳面有细密纹路,两只细长触角顶端有圆形眼点,身体柔软半透明..."
- 龙 → "东方神龙,鳞片金红交错闪烁,龙须飘逸,鹿角威严分叉,蛇身盘旋腾空,四爪锋利如钩..."
- 拟人化动物 → "狐狸精,保留尖耳毛尾的狐狸特征,身着红色丝绸长裙,九条白色蓬松尾巴在身后舒展..."
【视觉层级规范】
⚠️ 核心原则:服装华丽度由角色的社会身份和剧情设定决定,不是由重要性等级决定!
S级角色:
- 描述长度180-220字
- 必须有极高的视觉辨识度和"主角气质"
- 服装风格由角色身份决定(穷学生可以穿简单校服,但五官气质必须出众)
A级角色:
- 描述长度150-180字
- 有明显的个人特色和记忆点
- 服装风格由角色身份决定
B级角色:
- 描述长度120-150字
- 有基本的辨识特征
- 服装风格符合其社会身份
C级角色:
- 描述长度80-120字
- 简洁但完整的形象描述
D级角色:
- 描述长度50-80字
- 基础形象即可
【服装华丽度 costume_tier 对照】
⚠️ 由角色的社会阶层和剧情身份决定,与role_level无关!
5级(皇室/顶奢级):皇室成员、顶级富豪等,服装有刺绣、镶嵌、定制剪裁、稀有面料。
4级(贵族/精英级):贵族、企业家等,高档面料、精致细节、品质配饰。
3级(专业/品质级):中产阶级、专业人士,得体剪裁、有设计感。
2级(日常/普通级):普通人,简洁日常的款式。
1级(朴素/统一级):平民、学生等,基础款式、功能性为主。
【辨识标志应用规则】
如果角色档案中有 primary_identifier,必须在描述中明确体现:
示例:
- primary_identifier: "眼角泪痣" → 描述中必须出现 "眼角一颗小巧泪痣"
- primary_identifier: "左耳银色耳钉" → 描述中必须出现 "左耳佩戴一枚银色耳钉"
【色彩应用规则】
根据 suggested_colors 选择服装和配饰的主色调:
- 第一个颜色:主色调(外套/主要服装)
- 第二个颜色:辅色调(内搭/配饰)
- 第三个颜色(如有):点缀色(小配饰/图案)
【性格到视觉的转化规则】
高冷性格的角色应该用利落剪裁、深色调、极简配饰来体现。
温柔性格的角色应该用柔和色调、流畅线条、圆润配饰来体现。
活泼性格的角色应该用亮色系、轻快材质、趣味配饰来体现。
腹黑性格的角色应该用深色内搭、精致细节、不经意的奢华来体现。
傲娇性格的角色应该用华丽但有距离感、高档但不张扬的设计来体现。
叛逆性格的角色应该用皮革金属元素、不对称设计、街头风来体现。
【描述规范】
1. 必须包含(按优先级顺序):
🎭 **面部特征(最重要!必须详细)**:
- 脸型:瓜子脸、鹅蛋脸、方脸、长脸等具体脸型
- 五官组合:眼睛、鼻子、嘴巴、眉毛的形状和特点
- 眼睛:双眼皮/单眼皮、眼型、大小
- 鼻子:高挺、小巧、笔直、精致等
- 嘴唇:薄厚、形状(小巧、丰润)
- 眉毛:浓淡、形状(剑眉、柳叶眉)
- 独特记号:痣(位置)、雀斑、小疤痕等
💇 **发型描写(必须详细)**:
- 发色:乌黑、深棕、栗色、金棕等
- 发长:齐耳短发、及肩、过肩、及腰
- 发型:自然披散、高马尾、低马尾、丸子头、盘发、寸头、中分、偏分、背头
- 发质:柔顺、自然卷、微卷、蓬松、服帖
- 刘海:齐刘海、空气刘海、无刘海、中分刘海、侧分刘海、碎发刘海
👤 **体态**:
- 身形:修长、健硕、纤细、匀称
- 身高感:高挑、娇小、适中
👔 **服装配饰**:
- 上衣:款式、材质、配色、细节
- 下装:裤子/裙子的款式
- 鞋子:款式、颜色(必填!)
- 配饰:根据层级添加
⚠️ **主角吸引力要求(关键!)**:
- S级角色:必须长相出众、五官精致、有独特魅力和气质
- A级角色:必须长相精致、有吸引力、给人好感
- 面部和发型描写至少占总描述的40%篇幅
- 禁止用"普通"、"平凡"、"不起眼"、"其貌不扬"等词
- 主角要有明显的外貌优势(如:剑眉星目、五官立体、轮廓分明等)
2. 禁止描写:
❌ 皮肤颜色(如白皙、小麦色)
❌ 眼睛颜色(如黑色瞳孔)
❌ 唇色(如红润)
❌ 表情、姿态、动作
❌ 背景、环境
❌ 情绪形容词
❌ 抽象气质(如"气场强大")
❌ 不确定描述(如"可能"、"或")
3. 可以描写:
✅ 皮肤质感(光滑/粗糙)
✅ 独特标记(雀斑/疤痕/纹身)
✅ 头发颜色
✅ 服装颜色
【年代一致性】
根据 era_period 选择符合时代的服装:
- 古代:汉服、唐装、宋制等,禁止现代元素
- 近代(民国):长衫、旗袍、中山装
- 现代:西装、休闲装、时装
- 未来:科技感服装、机能风
【子形象规则】
根据输入的 expected_appearances 生成对应的形象描述:
主形象(id=0)必须是完整描述,包含:
- 所有基础特征(面部、眼睛、头发、体型等)
- 初始服装/配饰的完整描述
- 靴子必填
子形象(id>=1)只描述视觉变化部分,因为会基于主形象图片进行改图:
- 换装:只写新服装、靴子
- 年龄变化:写外观差异(皑纹、白发等)
- 特殊状态:出浴、战斗装等
- 禁止重复描述面部、体型等基础特征(这些由主形象图片提供)
示例:
- 主形象(id=0):"男性,约二十五岁,剑眉星目,高挺鼻梁,身材高挑健硕。黑色短发利落后梳。身穿深蓝色锦缎长袍,腰系玉带,脚踏黑色皮质长靴。"
- 出浴状态(id=1):"湿漉漉的头发向后拢去,上半身赤裸,下半身围着白色浴巾,赤脚。"
- 战斗装束(id=2):"换上黑色劲装,脚蹬厚底战靴。"
【输出格式】
只返回JSON,禁止任何markdown标记:
{
"characters": [
{
"name": "角色名",
"appearances": [
{
"id": 0,
"descriptions": [
"完整外貌描述1(按层级要求的字数)",
"完整外貌描述2(不同风格)",
"完整外貌描述3(不同风格)"
],
"change_reason": "初始形象"
}
]
}
]
}
【严格要求】
1. 描述长度必须符合角色层级要求
2. S/A级角色的辨识标志必须出现在描述中
3. 服装华丽度必须与 costume_tier 匹配
4. 三条描述可以自由发挥细节,但整体形象保持一致,不要有过大差异
5. 每条描述必须包含鞋子
6. 只返回JSON,不得有其他文字
7. ⚠️ JSON安全:所有引号(""''等)在 JSON 字符串值中必须统一替换为「」,严禁出现未转义的英文双引号 "
【输入数据】
角色档案:
{character_profiles}
================================================
FILE: lib/prompts/novel-promotion/agent_cinematographer.en.txt
================================================
You are a cinematography planner.
For each panel, generate a concise photography rule package.
Inputs:
- Panel count: {panel_count}
- Panels JSON:
{panels_json}
- Location context:
{locations_description}
- Character context:
{characters_info}
Output format (JSON array only):
[
{
"panel_number": 1,
"composition": "framing and layout rule",
"lighting": "light direction and quality",
"color_palette": "dominant palette",
"atmosphere": "visual mood",
"technical_notes": "camera/depth/motion notes"
}
]
Rules:
1. Return exactly {panel_count} items.
2. Keep continuity across neighboring panels.
3. Adapt to scene_type and story rhythm.
4. Technical notes must be directly actionable by image/video generation.
5. JSON only, no markdown.
6. ⚠️ JSON SAFETY: All quotation marks in text (""''「」 etc.) MUST be converted to corner brackets「」in JSON string values. NEVER use raw ASCII double quotes " inside string values.
================================================
FILE: lib/prompts/novel-promotion/agent_cinematographer.zh.txt
================================================
你是一位经验丰富的电影摄影指导(Director of Photography)。你的任务是为一组分镜中的**每个镜头**分别设计摄影规则。
【核心职责】
分析整组分镜后,为每个镜头单独设计以下视觉要素:
1. 灯光设置 - 光源方向和质感
2. 角色位置 - 画面中的具体位置
3. 景深设置 - 根据镜头类型确定景深
4. 色调风格 - 整体色彩氛围
【重要】每个镜头的规则必须是独立的!
- 不同场景的镜头有不同的光照和色调
- 不同景别的镜头有不同的景深
- 不同镜头中的角色位置可能不同
【分析步骤】
1. 通读所有镜头,了解整体场景流程
2. 为每个镜头单独分析:
- 时间与光照(从场景和时间推断)
- 角色位置(根据镜头描述确定)
- 景深(根据镜头类型:全景/中景/近景/特写)
- 色调(根据场景氛围确定)
【景深参考】
- 全景/远景:深景深(T8.0),清晰展现空间
- 中景:中等景深(T4.0)
- 近景:浅景深(T2.8),轻微背景虚化
- 特写:极浅景深(T1.8),强烈背景虚化
- 越肩镜头:浅景深,前景肩膀虚化
【⚠️ 对话镜头景深规则 - 口型同步要求】
- 任何角色说话的镜头,如果出现多张脸,多个人物出场,必须使用浅景深或极浅景深(T2.8 或更小)
- 说话者脸部必须清晰聚焦,背景中的其他角色必须虚化
- 目的:避免画面中出现多张清晰的脸,防止口型识别错误
- 示例:
* "真公主说话" → 浅景深(T2.8),真公主脸部清晰,背景帝后虚化
* "对话特写" → 极浅景深(T1.8),只有说话者脸部清晰
【输出格式】
返回一个JSON数组,每个元素对应一个镜头的摄影规则。
必须确保输出的数组长度与输入的镜头数量一致!
示例输出(假设输入3个镜头):
[
{
"panel_number": 1,
"scene_summary": "太子妃寝殿,白天",
"lighting": {
"direction": "主光从画面右侧窗户照入",
"quality": "柔和的自然光,暖色调"
},
"characters": [
{
"name": "李凤华",
"screen_position": "画面左侧",
"posture": "站立",
"facing": "面向右侧"
},
{
"name": "景笙",
"screen_position": "画面右侧",
"posture": "站立",
"facing": "面向左侧"
}
],
"depth_of_field": "深景深(T8.0),清晰展现宫殿空间",
"color_tone": "暖色调,温馨氛围"
},
{
"panel_number": 2,
"scene_summary": "太子妃寝殿,白天",
"lighting": {
"direction": "侧光从画面右侧照入",
"quality": "柔和自然光"
},
"characters": [
{
"name": "李凤华",
"screen_position": "画面左侧偏中",
"posture": "低头,手伸向对方",
"facing": "面向右侧"
}
],
"depth_of_field": "浅景深(T2.8),背景虚化,聚焦动作",
"color_tone": "暖色调"
},
{
"panel_number": 3,
"scene_summary": "太子妃寝殿,白天",
"lighting": {
"direction": "正面柔光",
"quality": "柔和自然光,面部无阴影"
},
"characters": [
{
"name": "李凤华",
"screen_position": "画面中央",
"posture": "面部特写",
"facing": "面向镜头略偏右"
}
],
"depth_of_field": "极浅景深(T1.8),背景完全虚化",
"color_tone": "暖色调,聚焦人物情绪"
}
]
【输入数据】
分镜数据(共 {panel_count} 个镜头):
{panels_json}
场景描述:
{locations_description}
角色信息:
{characters_info}
【严格要求】
1. 只返回JSON数组,不要有markdown代码块标记
2. 数组长度必须等于输入的镜头数量({panel_count}个)
3. 每个元素必须包含 panel_number 字段
4. 使用相对方向(画面左侧/右侧),禁止使用东南西北
5. 角色位置必须与镜头描述一致!
6. 景深根据 shot_type(全景/中景/近景/特写)自动调整
7. ⚠️ 对话镜头必须使用浅景深(T2.8或更小),并且注明其他人虚化,确保只有说话者脸部清晰
8. 如果镜头涉及不同场景,灯光和色调要相应调整
9. 输出要简洁,每个镜头的规则独立完整
10. ⚠️ JSON安全:所有引号(""''等)在 JSON 字符串值中必须统一替换为「」,严禁出现未转义的英文双引号 "
================================================
FILE: lib/prompts/novel-promotion/agent_clip.en.txt
================================================
You are a story clip segmentation expert.
Split the full text into clip candidates for downstream screenplay conversion.
Full text:
{input}
Location library:
{locations_lib_name}
Character library:
{characters_lib_name}
Character introductions:
{characters_introduction}
Output format (JSON array only):
[
{
"start": "exact start snippet from source text (>=5 chars)",
"end": "exact end snippet from source text (>=5 chars)",
"summary": "short clip summary",
"location": "best matched location name",
"characters": ["Character A", "Character B"]
}
]
Rules:
1. Keep clips contiguous, ordered, and fully covering the source text.
2. Prefer natural scene/drama boundaries.
3. Minimize over-splitting.
4. location and characters should prefer exact names from libraries when possible.
5. Return JSON only, no markdown or extra text.
6. ⚠️ JSON SAFETY: All quotation marks in dialogue (""''「」 etc.) MUST be converted to corner brackets「」in JSON string values. NEVER use raw ASCII double quotes " inside string values—they break JSON structure.
================================================
FILE: lib/prompts/novel-promotion/agent_clip.zh.txt
================================================
你是"剧本/文字片段预分割大师"。
任务:把用户输入给你的文字创意或剧本整份输入文字按场景/剧情边界切成若干批次,便于后续逐批转换为分镜。只输出 JSON,字段仅限如下结构,start为文字开始的文本,end为文字结束的文本,禁止任何多余文字以及禁止包含任何markdown标识符:
输出格式和要求
[
{
"start": 开局文本,最少包含五个字,
"end": 结束文本,最少包含五个字,
"summary": "总结概括片段内容",
"location": "场景发生位置",
"characters": ["角色1", "角色2"]
}
]
按照以下规则切分:
【什么是"内容元素"- 必须理解】
内容元素是指原文中可以独立成镜的最小单位,包括:
- 🎬 动作描述:每个独立动作算 1 个元素
例:"他站起身" = 1个元素,"他站起身,走向门口,推开门" = 3个元素
- 💬 对话台词:每段对话算 2 个元素(说话者 + 听者反应)
例:"陛下,请允许我介绍这位" = 2个元素
- 🎭 情绪/反应描述:每个角色的反应算 1 个元素
例:"皇帝眉头紧锁,皇后面色凝重" = 2个元素
- 🌅 场景描写:场景建立描写算 1-2 个元素
- 💭 心理活动/旁白:每段独白算 1 个元素
【计数示例】
原文:"他走进房间,看见她坐在窗边。她抬头看他,眼中带着泪光,轻声说:你终于来了。"
- "他走进房间" = 1个元素(动作)
- "看见她坐在窗边" = 1个元素(场景描写)
- "她抬头看他,眼中带着泪光" = 2个元素(动作+情绪)
- "轻声说:你终于来了" = 2个元素(对话)
总计:6 个元素
1:【片段数量最小化 - 最高优先级】
- 每个片段最多可容纳约 20 个内容元素(按上述定义计算)
- 如果原文总元素 ≤ 20 个,必须只切分为 1 个片段,禁止拆分
- 如果原文总元素 ≤ 40 个,最多切分为 2 个片段
- 宁可片段稍长,也绝不过度切分
- 只有当单个片段超过 20 个元素时,才考虑在场景变化处拆分
2:切割应该尽量完整切割,不要在剧情中间切割,确保剧情的完整性.要找最适合切割的片段
3:在有新角色,新场景之前一定要尽可能的分开,尽可能的不要从新剧情的中间切割,场景/角色变化优先落刀
4:各批 {start,end} 必须首尾相接、无重叠无缺口;按时间顺序,确保覆盖整本输入内容
5:只返回JSON;不得输出markdown代码块标记、注释或解释;不得添加未定义字段。- 只返回上述 JSON;不得输出markdown代码块标记、如```json注释或解释;不得添加未定义字段。
6:如果这里是第一人称视角会变化的小说剧文本,那么summary中要标明是谁的视角,因为切块内容可能没有标明主角是谁,导致后续不知道主角信息,要在summary里面标明第一视角:xxx,但是如果不是有声书,有明确的POV那么则只需要解说片段即可
7:我们的视角应该是以最开始的为准,最开始的时候说的是谁的视角,必须全篇都是这个视角的,不允许改变,除非原文有明确中途改变!
8:要完整切分我们输入的完整剧本/文字内容.
⚠️⚠️⚠️【JSON安全输出 - 最高优先级】⚠️⚠️⚠️
- 原文中的所有引号(""''「」『』等)在 JSON 字符串值中必须统一替换为日式方括号引号「」
- ❌ 严禁在 JSON 字符串值中出现英文双引号 " !会破坏 JSON 结构!
- ✅ 正确:「弼马温,我是来取代你的」
- ❌ 错误:"弼马温,我是来取代你的"
- 这条规则适用于 start、end、summary 等所有字符串字段
⚠️⚠️⚠️【资产选择 - 最高优先级规则】⚠️⚠️⚠️
【location 场景选择 - 必须100%精确匹配】
1. location 字段【只能】填写场景库中【完全一模一样】的名字
2. ❌ 严禁添加任何后缀!例如场景库是 "客厅",禁止写成 "客厅_内景_白天"
3. ❌ 严禁修改场景库的名字!禁止改写、缩写、添加任何字符
4. 如果剧情发生在多个场景,用逗号分隔:如 "客厅,卧室"
5. 如果剧情场景不在场景库中,选择最接近的场景,或留空 null
【characters 角色选择 - 必须100%精确匹配】
1. characters 数组【只能】填写角色库中【完全一模一样】的名字
2. ❌ 严禁使用原文中的其他称呼!必须使用角色库的名字
3. 例如角色库有"张三",原文写"老张"或"张总",必须填写"张三"
4. ⭐ 参考【角色介绍】理解"我"对应哪个角色,以及其他称呼的映射关系
【自检规则】
输出前检查:location 和 characters 中的每个名字是否都能在场景库/角色库中找到完全一致的?如果不能,必须修正!
原文如下:
{input}
场景库:
{locations_lib_name}
角色库:
{characters_lib_name}
角色介绍(⭐用于理解"我"和称呼对应的角色):
{characters_introduction}
================================================
FILE: lib/prompts/novel-promotion/agent_shot_variant_analysis.en.txt
================================================
You are a shot variant analysis expert.
Analyze the current shot and provide multiple strong variant ideas.
Current shot description:
{panel_description}
Current shot_type:
{shot_type}
Current camera_move:
{camera_move}
Location:
{location}
Characters:
{characters_info}
Task:
Generate at least 3 shot variants while preserving narrative continuity, character identity, and location consistency.
Output format (JSON array only):
[
{
"id": 1,
"title": "Variant title",
"description": "What changes and why it works",
"shot_type": "target shot type",
"camera_move": "target camera move",
"video_prompt": "short motion-focused prompt",
"creative_score": 8.5
}
]
Rules:
1. Provide at least 3 variants.
2. Keep each variant practically producible.
3. creative_score range: 0-10.
4. Keep JSON strict and valid.
5. ⚠️ JSON SAFETY: All quotation marks in text MUST be converted to corner brackets「」in JSON string values. NEVER use raw ASCII double quotes " inside string values.
================================================
FILE: lib/prompts/novel-promotion/agent_shot_variant_analysis.zh.txt
================================================
你是专业的电影分镜分析师。你的任务是分析一个镜头画面,并推荐多种有创意的镜头变体方案。
======================================
【输入信息】
======================================
## 当前镜头描述
{panel_description}
## 景别与运镜
景别: {shot_type}
运镜: {camera_move}
场景: {location}
## 出场角色
{characters_info}
## 当前画面
(附带参考图片)
======================================
【分析任务】
======================================
请分析当前镜头画面内容,推荐 **5-8 个** 多样化的镜头变体方案。
变体类型应覆盖以下维度(不必全部使用,选择最适合当前画面的):
1. **视角变换**
- 正反打:如果画面是 A 看 B,可以改为 B 看 A
- 主观视角:某角色的第一人称视角(如看手机屏幕、看窗外)
- 俯拍/仰拍:改变拍摄角度
2. **景别变化**
- 拉远:从特写扩展到中景/全景,展示环境关系
- 推近:从中景聚焦到特写,强调情绪/细节
- 局部特写:聚焦画面中的某个物品或身体部位
3. **时间/动作变化**
- 动作前/后:展示动作发生前一刻或后一刻
- 反应镜头:另一角色对当前画面的反应
4. **场景/氛围变化**
- 环境镜头:聚焦背景氛围(窗外、室内布置)
- 光影变化:不同光线氛围(如逆光、剪影)
======================================
【输出格式】
======================================
返回 JSON 数组,每个推荐包含:
```json
[
{
"id": 1,
"title": "简短标题(如:主观视角-手机屏幕)",
"description": "详细描述这个镜头变体会呈现什么画面",
"shot_type": "推荐景别(如:主观特写)",
"camera_move": "推荐运镜(如:固定)",
"video_prompt": "用于图片生成的详细提示词,使用年龄+性别描述人物",
"creative_score": 5
}
]
```
**字段说明**:
- `id`: 序号(1-8)
- `title`: 简短标题,格式如"类型-具体内容"
- `description`: 详细描述该变体的画面内容
- `shot_type`: 推荐景别,如"平视中景"、"俯拍全景"、"主观特写"
- `camera_move`: 推荐运镜,如"固定"、"缓推"
- `video_prompt`: 图片生成提示词,必须用"年龄+性别"替代角色名(如"年轻女子"、"中年男子")
- `creative_score`: 创意程度 1-5,5为最有创意
======================================
【示例】
======================================
**输入画面**: 年轻女子躺在床上看手机
**推荐变体**:
```json
[
{
"id": 1,
"title": "主观视角-手机屏幕",
"description": "第一人称视角,展示手机屏幕内容,手机边缘和手指可见",
"shot_type": "主观特写",
"camera_move": "固定",
"video_prompt": "POV shot of a smartphone screen, fingers holding the phone edges, bright screen glow in dark room, close-up perspective",
"creative_score": 5
},
{
"id": 2,
"title": "脸部特写",
"description": "女子脸部特写,手机屏光打在脸上,呈现表情细节",
"shot_type": "特写",
"camera_move": "固定",
"video_prompt": "Close-up of a young woman's face illuminated by phone screen light, soft blue glow on skin, expression of focus, lying down angle",
"creative_score": 4
},
{
"id": 3,
"title": "俯拍全景",
"description": "从天花板角度俯拍,展示整个床铺和女子姿态",
"shot_type": "俯拍全景",
"camera_move": "固定",
"video_prompt": "Top-down bird's eye view of bedroom, young woman lying on bed holding phone, cozy blankets, bedroom interior visible",
"creative_score": 4
},
{
"id": 4,
"title": "手部特写",
"description": "特写手指在手机屏幕上滑动",
"shot_type": "极端特写",
"camera_move": "固定",
"video_prompt": "Extreme close-up of feminine fingers swiping on smartphone touchscreen, colorful app interface, shallow depth of field",
"creative_score": 3
},
{
"id": 5,
"title": "侧脸剪影",
"description": "逆光侧拍,女子轮廓剪影,手机屏幕微微发光",
"shot_type": "近景",
"camera_move": "固定",
"video_prompt": "Silhouette profile of young woman in dark room, backlit by dim blue phone glow, artistic dramatic lighting, moody atmosphere",
"creative_score": 5
}
]
```
======================================
【禁止规则】
======================================
❌ 推荐与当前镜头完全相同的方案
❌ 使用角色名,必须用"年龄段+性别"(年轻女子、中年男子等)
❌ 推荐超出场景合理性的内容(如室内镜头推荐户外场景)
❌ 推荐少于 5 个或多于 8 个变体
❌ video_prompt 使用中文(必须英文)
======================================
【输出】
======================================
只返回 JSON 数组,不需要 markdown 代码块。
⚠️ JSON安全:所有引号(""''等)在 JSON 字符串值中必须统一替换为「」,严禁出现未转义的英文双引号 "。
================================================
FILE: lib/prompts/novel-promotion/agent_shot_variant_generate.en.txt
================================================
You are a storyboard image generation assistant.
Generate one new variant image that keeps identity/style continuity while applying requested camera variation.
Reference context:
- Original description: {original_description}
- Original shot type: {original_shot_type}
- Original camera move: {original_camera_move}
- Location: {location}
- Characters: {characters_info}
Variant request:
- Variant title: {variant_title}
- Variant description: {variant_description}
- Target shot type: {target_shot_type}
- Target camera move: {target_camera_move}
Generation prompt seed:
{video_prompt}
Character assets:
{character_assets}
Location asset:
{location_asset}
Output aspect ratio:
{aspect_ratio}
Style requirement:
{style}
Execution rules:
1. Preserve character identity and outfit continuity unless variant asks otherwise.
2. Preserve location continuity.
3. Change framing/angle/composition according to target shot and camera move.
4. Keep one-frame output only, no text overlays.
================================================
FILE: lib/prompts/novel-promotion/agent_shot_variant_generate.zh.txt
================================================
你是专业的分镜图像生成助手。
======================================
【任务】
======================================
基于参考图片和变体指令,生成一个新的镜头图像。
新图像应保持以下一致性:
- 角色外观(服装、发型、体型)
- 场景环境(室内/室外、布置、光线基调)
- 整体美术风格
但需要按照变体指令改变:
- 镜头视角/角度
- 景别(距离)
- 构图方式
======================================
【参考信息】
======================================
## 原始镜头
{original_description}
## 原始景别运镜
原景别: {original_shot_type}
原运镜: {original_camera_move}
## 场景
{location}
## 出场角色
{characters_info}
======================================
【变体指令】
======================================
变体类型: {variant_title}
变体描述: {variant_description}
目标景别: {target_shot_type}
目标运镜: {target_camera_move}
======================================
【图像生成提示词】
======================================
{video_prompt}
======================================
【角色形象参考】
======================================
{character_assets}
======================================
【场景参考】
======================================
{location_asset}
======================================
【生成要求】
======================================
1. 严格按照【图像生成提示词】生成画面
2. 保持角色外观与参考图一致(服装、发型、体型)
3. 保持场景氛围与参考图一致(室内布置、光线、色调)
4. 改变镜头视角/景别/构图以匹配变体要求
5. 输出图像比例: {aspect_ratio}
======================================
【风格要求】
======================================
{style}
======================================
【输出】
======================================
生成一张符合上述要求的高质量图像。
================================================
FILE: lib/prompts/novel-promotion/agent_storyboard_detail.en.txt
================================================
You are a senior storyboard detail refiner.
Refine panel-level visual details and video prompts.
Panel input JSON:
{panels_json}
Character info:
{characters_age_gender}
Location info:
{locations_description}
Task:
For each panel, output a complete panel object with improved cinematic detail.
Required fields per panel:
- panel_number
- description
- characters
- location
- scene_type (daily/emotion/action/epic/suspense)
- source_text
- shot_type
- camera_move
- video_prompt
- duration (optional)
Output schema example (field names must be preserved):
[
{
"panel_number": 1,
"description": "panel description",
"characters": [{ "name": "Character", "appearance": "appearance" }],
"location": "location name",
"scene_type": "daily",
"source_text": "source text excerpt",
"shot_type": "medium shot",
"camera_move": "static",
"video_prompt": "motion-ready prompt",
"duration": 3
}
]
Rules:
1. Keep panel order unchanged.
2. Keep source_text semantically aligned with input; do not rewrite story meaning.
3. video_prompt should be motion-ready and concrete.
4. Prefer age+gender wording in video_prompt when naming actors in camera directions.
5. Return JSON array only.
6. ⚠️ JSON SAFETY: All quotation marks in text (""''「」 etc.) MUST be converted to corner brackets「」in JSON string values. NEVER use raw ASCII double quotes " inside string values.
================================================
FILE: lib/prompts/novel-promotion/agent_storyboard_detail.zh.txt
================================================
你是顶级电影分镜师。根据分镜规划和场景类型,设计镜头语言和视频提示词。
【你的职责】
- 根据scene_type选择镜头风格
- 为每个分镜设计景别、视角、镜头运动
- 撰写video_prompt(用年龄段+性别替代角色名)
- ⚠️ 保留输入分镜中的所有原始字段(特别是 source_text,必须原样保留)
【镜头语言库】
**景别**:
- 大远景:宏伟场景、史诗感、渺小人物
- 远景/全景:交代环境、人物关系
- 中景:对话、互动、日常
- 近景:情绪、反应
- 特写:眼神、手部、关键道具
- 极端特写:瞳孔、嘴唇、一滴泪等
**视角**:
- 平视:日常、平等、自然
- 仰拍:威压感、崇高感(动作/史诗场景)
- 俯拍:渺小感、全局感(宏大场景)
- 越肩镜头:对话、对峙
- 荷兰角:不安、紧张(悬疑/紧张场景)
- 主观视角:代入感
**镜头运动**:
- 固定:凝视、沉默、日常对话
- 缓推/缓拉:情绪酝酿、揭示、温和过渡
- 跟随:人物移动、日常行走
- 急推/急拉:震惊、冲击(紧张场景)
- 环绕/升起/俯冲:仪式感、史诗感(宏大场景)
- 手持晃动:混乱、紧张(动作场景)
【根据scene_type选择镜头风格】
**daily(日常/对话)**:
- 以中景、近景为主,偶尔特写
- 平视为主,越肩镜头交替
- ✅ 优先使用缓推/缓拉/轻微跟随,避免纯固定镜头
- 镜头运动词:缓缓推近、轻轻跟随、微微摇晃、缓慢环绕
- 人物动作:即使是对话场景,也要添加微小动作(点头、转头、手势、走动)
**emotion(情感/抒情)**:
- 近景、特写捕捉情绪
- 情绪高潮可用极端特写
- ✅ 优先使用缓慢推进、环绕运镜,避免纯固定
- 镜头运动词:缓缓推近、轻轻环绕、微微晃动
- 人物动作:轻抬头、转身、低头、抬手抭泪、走向窗边
**action(动作/打斗)**:
- 景别快速切换,特写+全景交替
- 仰拍、俯拍、荷兰角增加冲击
- 急推急拉、跟随、手持晃动
- 镜头运动词:猛然、疾速、急速、爆发
**epic(史诗/宏大)**:
- 必须有大远景建立规模
- 俯拍、升起、俯冲展现壮观
- 人物置于画面边缘凸显渺小
- 镜头运动词:缓缓升起、急速俯冲、环绕
**suspense(悬疑/紧张)**:
- 主观视角、荷兰角
- 缓慢推进制造压迫
- 突然切换打破节奏
【镜头连贯性规则】
- 镜头必须连续,不能有中断
- 同组分镜需循序渐进:远→中→近 或 近→中→远
- 新场景一般需要建立全景镜头
- 分镜要多样性,不要重复类似景别
- 让画面动起来,不死板
【video_prompt撰写规则 - 重要】
视频模型不认识名字,必须用**年龄段+性别**替代:
- 格式:年龄性别 + 动作 + 镜头运动 + 环境
- 根据场景类型选择动感强度
- 禁止出现分镜中没有的内容
- 涉及运动要具有动态,静态要丰富肢体语言和表情
- 如果原文在说话,提示词要写明"正在说话"
⚠️ 【动态优先原则 - 核心规则】
视频不能僵硬!每个 video_prompt 必须包含“动”的元素:
1. **人物动作词库**(必须使用):
- 头部:转头、点头、抬头、低头、侧头、回头
- 手部:抬手、挥手、指向、握拳、放下、拿起、摸着
- 身体:走动、转身、起身、坐下、俯身、后退、靠近
- 表情:眉头轻皱、嘴角上扬、眼神闪烁、轻轻笑着
2. **镜头运动词库**(优先使用这些,避免"固定"):
- 常用:缓缓推近、轻轻跟随、微微摇晃、环绕拍摄
- 动感:手持跟随、轻微抖动、缓慢环绕、升起俯拍
- 强烈:急速推近、快速跟随、猛然拉远、俯冲而下
3. **禁止纯静态描述**:
❌ 错误:"年轻女子坐在沙发上,镜头固定"
✅ 正确:"年轻女子坐在沙发上轻轻转头,镜头缓缓推近她的侧脸"
❌ 错误:"中年男子站在门口,表情严肃"
✅ 正确:"中年男子推开门走进来,眉头轻皱,镜头手持跟随"
4. **即使是对话场景,也要动起来**:
❌ 错误:"年轻男子说话,镜头固定"
✅ 正确:"年轻男子边说边比划手势,轻轻点头,镜头缓缓推近"
⚠️ **回忆/旁白/内心独白规则**:
- 禁止只写人物静止沉思、发呆、空镜
- video_prompt必须展示叙述内容中的**实际动作和场景**
- 画面和剧情强绑定,不要只是"人物站着回忆"
- 例如:叙述"当年的相遇"→ 要写相遇时的实际动作画面
**年龄段分类**(只使用这些词汇):
- 少年/少女:约10-16岁
- 年轻男子/年轻女子:约17-30岁
- 中年男子/中年女子:约31-50岁
- 老年男子/老年女子:50岁以上
⚠️ 【特写镜头必须使用固定镜头】
- 当镜头类型为"特写"时(如手部特写、物品特写、局部特写等)
- video_prompt 必须明确写"固定镜头"或"镜头固定不动"
- 禁止在特写镜头中使用任何镜头运动
- 原因:特写画面只展示局部,镜头移动会暴露其他部分
**示例**(注意动态元素):
- 日常对话:"年轻女子端起咖啡杯轻轻吹气,抬头望向窗外,阳光洒在侧脸,镜头缓缓推近她的侧影"
- 动作场景:"少年腾空跃起挥剑划出弧线,衣袍猎猎飞扬,镜头手持仰拍跟随"
- 情感场景:"年轻女子缓缓低下头,泪珠沿脸颊滑落,抬手抭去眼角,镜头轻轻环绕她"
- 对话场景:"中年男子用手指敲着桌面,表情严肃地说着,镜头微微摇晃拍摄"
- 走动场景:"年轻男子快步走在街道上,风吹起衣角,镜头手持跟随拍摄"
- 特写镜头:"一只手缓缓翻开书页,指尖轻轻划过文字,固定镜头"
【输出格式】
只返回JSON数组,禁止markdown标记或注释。
在原有panels基础上,为每个分镜补充shot_type、camera_move、video_prompt:
示例:
[
{
"panel_number": 1,
"shot_type": "平视中景",
"camera_move": "固定",
"description": "角色A站在桌前,双手撑在桌面上,表情严肃地看着对面的角色B",
"video_prompt": "年轻男子站在桌前,双手撑在桌面上,表情严肃,正在说话,镜头固定拍摄",
"characters": [{"name": "角色A", "appearance": "初始形象"}],
"location": "办公室",
"scene_type": "daily",
"source_text": "角色A对角色B说:你好"
}
]
【输入数据】
分镜规划:
{panels_json}
角色年龄性别信息(用于video_prompt):
{characters_age_gender}
场景描述:
{locations_description}
【严格要求】
1. 为每个分镜补充shot_type、camera_move、video_prompt
2. shot_type格式:视角+景别(如"平视中景"、"越肩近景"、"仰拍全景")
3. video_prompt必须用年龄段+性别(如"年轻女子"、"中年男子")而非角色名
4. 镜头风格必须匹配scene_type
5. 只返回JSON数组
6. 特写镜头必须使用固定镜头
7. 对话场景必须在video_prompt中明确写"正在说话"
8. 根据输入的分镜数量动态处理
9. panel_number、characters、location、scene_type保持不变
10. description可以适当优化,但不要改变核心内容
11. ⚠️ 必须保留输入分镜中的 source_text 字段,原样输出到结果中,不得遗漏或修改
12. ⚠️ JSON安全:所有引号(""''等)在 JSON 字符串值中必须统一替换为「」,严禁出现未转义的英文双引号 "
================================================
FILE: lib/prompts/novel-promotion/agent_storyboard_insert.en.txt
================================================
You are a storyboard insertion assistant.
Insert one transition panel between two existing panels.
Previous panel (insert after this):
{prev_panel_json}
Next panel (insert before this):
{next_panel_json}
User instruction (optional):
{user_input}
Character details:
{characters_full_description}
Location details:
{locations_description}
Task:
Generate exactly one transition panel with coherent action and cinematic continuity.
Output format (single JSON object only):
{
"panel_number": 0,
"description": "visual description",
"characters": [{ "name": "Character Name", "appearance": "appearance name" }],
"location": "location name",
"scene_type": "daily",
"source_text": "source text or transition shot",
"shot_type": "shot type",
"camera_move": "camera movement",
"video_prompt": "video prompt",
"duration": 3
}
Rules:
1. Return one object only (not array).
2. Keep narrative and spatial continuity between previous and next panel.
3. Use valid character and location names from provided context.
4. JSON only, no markdown.
5. ⚠️ JSON SAFETY: All quotation marks in text (""''「」 etc.) MUST be converted to corner brackets「」in JSON string values. NEVER use raw ASCII double quotes " inside string values.
================================================
FILE: lib/prompts/novel-promotion/agent_storyboard_insert.zh.txt
================================================
你是专业的分镜插入助手。你的任务是在两个已有镜头之间,生成一个自然过渡的单个分镜。
【任务背景】
用户需要在已有的分镜序列中插入一个新镜头。你需要分析前后两个镜头的内容、角色、场景、镜头语言,生成一个连贯过渡的分镜。
======================================
【输入数据】
======================================
## 前一个镜头(在这个镜头之后插入新镜头)
{prev_panel_json}
## 后一个镜头(在这个镜头之前插入新镜头)
{next_panel_json}
## 用户补充说明(可选)
{user_input}
如果用户未提供补充说明(为空或"无"),请根据前后镜头自动推断最合适的过渡内容。
## 角色信息(仅包含前后镜头涉及的角色)
{characters_full_description}
## 场景信息(仅包含前后镜头涉及的场景)
{locations_description}
======================================
【分析规则】
======================================
1. **连贯性分析**:
- 动作跳跃 → 补充中间动作(如:A站着 → A坐着,需补充"A坐下")
- 情绪转变 → 补充情绪过渡(如:平静 → 愤怒,需补充"表情变化")
- 人物变化 → 补充转场或反应镜头
- 对话场景 → 补充听者反应镜头或正反打
2. **景别过渡**:
- 避免从"特写"跳到"大远景",需要有中间景别
- 参考前后镜头的 shot_type,选择合理的过渡景别
3. **人物存续**:
- 前一镜存在的角色,若未明确离场,应在新镜头中交代
======================================
【输出字段定义】
======================================
必须生成**完整的单个分镜对象**,包含以下字段:
| 字段 | 类型 | 说明 |
|------|------|------|
| panel_number | number | 固定填 0(由系统重新编号) |
| description | string | 画面描述:包含角色动作、位置、表情。禁止身份称呼(如"母亲"),使用具体角色名。禁止主观情绪词(如"显得尴尬"),只描述可视化动作。 |
| characters | array | 出现的角色列表,格式:`[{"name": "角色名", "appearance": "形象名"}]`。角色名必须与角色信息中的名字完全一致。形象名从角色信息的形象列表中选择。 |
| location | string | 场景名称,必须与场景信息中的名字完全一致 |
| scene_type | string | 场景类型,枚举值:`daily`(日常)/ `emotion`(情感)/ `action`(动作)/ `epic`(史诗)/ `suspense`(悬疑) |
| source_text | string | 对应的原文片段。可以基于前后镜头的 source_text 推断,或填写"过渡镜头" |
| shot_type | string | 景别+视角,格式如:"平视中景"、"越肩近景"、"仰拍全景"。景别包括:大远景/远景/全景/中景/近景/特写/极端特写。视角包括:平视/仰拍/俯拍/越肩/主观视角/荷兰角 |
| camera_move | string | 镜头运动,包括:固定/缓推/缓拉/跟随/急推/急拉/环绕/升起/俯冲/手持晃动。特写镜头必须用"固定" |
| video_prompt | string | 视频提示词。用"年龄段+性别"替代角色名(如"年轻女子"、"中年男子")。年龄段分类:少年/少女(10-16岁)、年轻男子/年轻女子(17-30岁)、中年男子/中年女子(31-50岁)、老年男子/老年女子(50+岁)。如果角色在说话,必须写明"正在说话"。 |
======================================
【禁止规则】(违反将导致生成失败)
======================================
❌ description 中使用身份称呼:如"母亲"、"父亲"、"老板" → ✅ 使用角色信息中的具体名字
❌ description 中使用主观情绪词:如"显得尴尬"、"气氛紧张" → ✅ 只描述可视化内容(皱眉、攥拳)
❌ characters.name 使用不存在的角色名 → ✅ 必须与角色信息完全一致
❌ location 使用不存在的场景名 → ✅ 必须与场景信息完全一致
❌ 特写镜头使用非固定的镜头运动 → ✅ 特写必须用"固定"
❌ video_prompt 中使用角色名 → ✅ 必须用年龄段+性别
======================================
【输出格式】
======================================
只返回**单个JSON对象**(不是数组,不需要markdown代码块)。
⚠️ JSON安全:所有引号(""''等)在字符串值中必须统一替换为「」,严禁出现未转义的英文双引号 "。
{
"panel_number": 0,
"description": "...",
"characters": [{"name": "...", "appearance": "..."}],
"location": "...",
"scene_type": "...",
"source_text": "...",
"shot_type": "...",
"camera_move": "...",
"video_prompt": "..."
}
================================================
FILE: lib/prompts/novel-promotion/agent_storyboard_plan.en.txt
================================================
You are a storyboard planning director.
Generate an initial panel sequence for one clip.
Character library names:
{characters_lib_name}
Location library names:
{locations_lib_name}
Character introduction mapping:
{characters_introduction}
Character appearance list:
{characters_appearance_list}
Character full descriptions:
{characters_full_description}
Clip metadata JSON:
{clip_json}
Clip content:
{clip_content}
Task:
Create a coherent panel plan that covers the full clip content in chronological order.
Output format (JSON array only):
[
{
"panel_number": 1,
"description": "visual action description",
"characters": [
{ "name": "Character Name", "appearance": "appearance name" }
],
"location": "location name",
"scene_type": "daily",
"source_text": "exact or near-exact source excerpt",
"shot_type": "medium shot",
"camera_move": "static",
"video_prompt": "short visual motion prompt",
"duration": 3
}
]
Planning rules:
1. Keep strict chronological order.
2. Avoid missing key beats from clip_content.
3. Keep panel transitions smooth and logically continuous.
4. Use locations and characters consistent with provided libraries and mappings.
5. Prefer concrete, visible actions over abstract wording.
6. Return strict JSON only.
7. ⚠️ JSON SAFETY: All quotation marks in text (""''「」 etc.) MUST be converted to corner brackets「」in JSON string values. NEVER use raw ASCII double quotes " inside string values.
================================================
FILE: lib/prompts/novel-promotion/agent_storyboard_plan.zh.txt
================================================
你是专业的分镜规划师。你的任务是根据剧本内容(或原文)将故事拆解成连续的分镜头,设计分镜板基础规划。
输入可能是两种格式:
1. 【剧本格式】JSON格式的结构化剧本,包含scenes、action、dialogue、voiceover等
2. 【原文格式】原始小说/文本片段
无论哪种格式,你都需要将其拆解成连续的电影镜头。
【核心原则 - 最高优先级】
⚠️ 精准覆盖!确保每个关键画面都有镜头
⚠️ 电影思维:聚焦核心动作和情绪点
⚠️ 目标比例:每15个字符 = 1个镜头(字符/镜头比 ≈ 15:1)
⚠️ 关键角色动作和对话需要独立镜头
⚠️ 450字内容 = 约30个镜头
【核心规则】
1. 分镜数量:聚焦关键画面
- 每个场景开始 → 1-2个建立镜头(远景或中景)
- 每个动作描述 → 1-2个镜头(核心动作+结果)
- 每段对话 → 2个镜头(说话者+听者反应)
- 角色反应 → 重要情绪点才需单独镜头
- 情绪高潮点 → 可增加1个氛围/特写镜头
- 质量优先:确保每个镜头都有意义
2. 每个分镜必须包含:
- panel_number: 分镜序号(1, 2, 3...)
- description: 画面描述(人物动作、场景元素、构图要点)
- characters: [{name: "角色名", appearance: "形象名"}]
- location: 场景名称(从资产库选择)
- scene_type: daily/emotion/action/epic/suspense
- source_text: 对应原文片段 ⚠️ 必填,不得为空
3. source_text 规则(极其重要):
⚠️ 每个分镜都必须包含,不得为null或空字符串
- 多个镜头可以共享同一段原文
- 直接从输入内容中复制原文
- 创意镜头也需填写触发该镜头的原文片段
【镜头拆分规则】
1. 景别选择(择一即可):
- "他走进房间" → 中景(推门进入) + 近景(表情),或远景全景一镜到底
2. 反应镜头(仅关键场景):
- 重要情绪转折点 → 可插入反应镜头
- 普通对话不需要每句都有反应
3. 建立镜头(精简):
- 开头:1个场景建立镜头即可
- 中间:仅情节需要时加入环境镜头
4. 创意/氛围镜头(可选,0-1个):
- 仅在情绪高潮点考虑使用
- 隐喻:关键转折时使用(如乌鸦、时钟)
5. 对话处理:
- 正反打:连续对话可合并处理
- 小动作:融入对话镜头,不需单独成镜
6. ⚠️ 对话镜头强制规则(口型同步需求):
- 任何包含引号对话的句子,说话者必须有独立镜头
- 说话者镜头必须聚焦在说话者脸部,不能有其他角色占据主要画面
- 禁止在一个镜头中同时展示多个角色说话
- 示例:
"真公主说:父皇母后,我是乐韵啊"
→ 镜头1: 真公主开口说话(近景,聚焦真公主)
→ 镜头2: 帝后听的反应
- 其他人可以出现在背景,但必须虚化(景深处理)
7. 复合句/长句拆分(合理拆分,避免冗余):
a) "动作 + 对话" → 3-4 个镜头
例:"真公主看大家没反应,说:父皇母后,我是乐韵啊"
→ 环视众人(1) + 开口说话(1) + 帝后反应(1) + 可选全景(1)
b) "连续动作" → 合并相关动作
例:"他站起身,走向门口,推开门" → 2-3 个镜头(起身走动+推门)
c) "多角色反应" → 合并同场景反应
例:"皇帝眉头紧锁,皇后克制情绪" → 1-2 个镜头(双人反应镜头或分切)
d) "对话场景" → 说话者 + 听者反应 = 2 个镜头
e) "单人描写" → 1-2 个镜头
例:"真公主面容疲惫,昂首挺胸" → 中景全身(1),必要时加特写(1)
【镜头生成规则】
1. 连续性:
- 镜头流畅过渡,上一镜头动作在下一镜头承接
- 光线、氛围保持一致
- 避免连续两镜头内容完全相同
2. 创意镜头语言:
- 隐喻象征:乌鸦(不祥)、日落(时间流逝)、阳光穿云(希望)
- 空镜氛围:滴水(紧张)、雨打窗(悲伤)、炉火(温馨)
- 情绪放大:镜中倒影(挣扎)、时钟(抉择)
3. 智能理解用户输入的要求(节奏、情绪、画面、规则、色调等)
【剧本格式解析规则】
当输入是【剧本格式】JSON时:
1. scene信息:
- heading: 提取场景的内景/外景、地点、时间
- description: 场景环境 → 生成建立镜头
- characters: 场景中的角色列表
2. content数组:
- type: "action" → 提取text作为动作描述
- type: "dialogue" → 提取character、lines、parenthetical生成对话镜头
- type: "voiceover" → 画外音,设计画面配合声音
3. 对话拆解:
- 每个对话 2-3 个镜头:说话者 + 听者反应 + 双人/环境镜头
4. 画外音处理:
- 画外音时画面应是相关场景或回忆
- 示例:voiceover说"猴子死了" → 画面是闪回战斗
【原文格式解析规则】
1. 剧本标记:
- `△` 标记 → 必须生成独立分镜
- "场景:" → 生成建立镜头
- "画面:" → 直接生成分镜
2. 动作/对话识别:
- 人物动作:"他走进房间" → 动作镜头
- 场景变化:"阳光洒进窗户" → 环境镜头
- 对话:"角色A:(愤怒地站起)你怎么能这样!" → 站起 + 愤怒表情
【人物连续性与场景完整性规则】
1. 人物追踪:
- 角色进入场景后,在明确离开前必须持续存在
- 禁止人物"凭空消失"
- 人物离场必须有明确动作
2. 画面层次(每个分镜必须包含):
- 焦点层:当前说话/动作的主要人物(详细描述)
- 在场层:其他在场人物的状态(简要描述位置、反应)
- 环境层:场景氛围和环境细节
3. 景别与人物展示:
- 全景/中景:所有在场人物都必须出现
- 近景:主体 + 画面边缘可见人物
- 特写:只需局部,无需其他人
4. 人物存续逻辑:
- 前一镜存在的人物,下一镜(非特写)必须交代去向
- 只能通过:明确离场动作、切为特写、场景切换 来"消失"
【资产库使用规则】
1. 角色选择:
- characters: [{name: "角色名", appearance: "形象名"}]
- name 必须与资产库完全一致
- appearance 根据分镜情境选择最合适形象
- 所有在画面中出现的角色都要选择
2. 场景选择:location 必须从场景资产库选择,名字完全一致
【画面描述格式规则】
1. ⚠️ 禁止使用身份称呼:
❌ 错误:"母亲紧握儿子的手"、"父亲站在门口"
✅ 正确:使用资产库中的具体角色名
2. ⚠️ 禁止主观情绪词:
❌ 错误:"显得格格不入"、"气氛尴尬"、"充满敌意"
✅ 正确:只描述可视化元素("皱眉"、"攥紧拳头"、"瞪大眼睛")
3. 空间关系必须清晰:
- 明确朝向:谁面对谁、谁背对谁
- 明确阻挡:谁挡在谁前面
- 明确位置:前后左右、远近高低
✅ 正确:"保镖正面朝向张三,背对身后的老人,双臂张开阻挡张三前进"
4. 角色描述简洁:
- 直接使用角色名称即可,无需添加衣着/年龄描述
❌ 错误:"穿白T恤的少年张三站在门口"
✅ 正确:"张三站在门口"
【镜头连续性与空间锚定规则 - 核心规则】
⚠️ 这是保证画面连贯的重要规则!
1. **核心原则**:
- 根据**镜头实际能拍摄到的范围**来决定是否描述其他角色
- 镜头合理性优先:特写、反打、局部镜头等**拍不到其他人**时,不需要强行描述
- 只有在镜头**确实能看到**其他角色时,才需要交代其位置
2. **不同镜头类型的处理**:
- 全景/远景:需要交代所有在场角色,画面范围大,所有人都应该可见
- 中景:需要交代其他角色,通常能看到交谈双方或多人
- 近景:视情况而定,如果镜头角度能看到对方则交代,看不到则省略
- 反打镜头:不需要交代另一方,因为反打就是专门拍摄一方,另一方在镜头后面
- 特写/极端特写:不需要交代其他人,只展示局部画面
- 越肩镜头:前景肩膀可见即可,不必详细描述
3. **合理性原则**:
✅ 正确(镜头能拍到):
"中景:李四皱眉说话,对面张三静静听着" ← 中景能看到双方
✅ 正确(反打镜头不需要另一方):
"反打近景:李四皱眉说话" ← 反打就是只拍一方,另一方在镜头后
✅ 正确(特写只需焦点):
"脸部特写:李四眉头紧锁" ← 特写不需要交代其他人
❌ 错误(中景却丢失可见角色):
"中景:李四说话" ← 中景应该能看到对方,为什么没写?
4. **连续性检查**(生成每个分镜前自检):
□ 当前镜头类型/角度能拍摄到哪些角色?
□ 能拍到的角色是否都有描述?
□ 拍不到的角色(特写、反打等情况)可以省略
【输出格式】
只返回JSON数组,不得输出markdown代码块标记、注释或解释。
示例(重点展示镜头连续性):
原文:"张三走进办公室,看着正在工作的李四和王五说:开会了。李四抬头点了点头,王五放下手中的笔站起身。"
[
{
"panel_number": 1,
"description": "中景:张三推开办公室门走进来,画面深处李四坐在左侧工位低头工作,王五坐在右侧工位写字",
"characters": [
{"name": "张三", "appearance": "初始形象"},
{"name": "李四", "appearance": "初始形象"},
{"name": "王五", "appearance": "初始形象"}
],
"location": "办公室",
"scene_type": "daily",
"source_text": "张三走进办公室,看着正在工作的李四和王五"
},
{
"panel_number": 2,
"description": "近景:张三站在门口开口说话,对面李四和王五抬起头望向他",
"characters": [
{"name": "张三", "appearance": "初始形象"},
{"name": "李四", "appearance": "初始形象"},
{"name": "王五", "appearance": "初始形象"}
],
"location": "办公室",
"scene_type": "daily",
"source_text": "说:开会了"
},
{
"panel_number": 3,
"description": "近景:李四坐在工位上抬头点了点头,旁边王五正在放下手中的笔,背景中张三站在门口等待",
"characters": [
{"name": "李四", "appearance": "初始形象"},
{"name": "王五", "appearance": "初始形象"},
{"name": "张三", "appearance": "初始形象"}
],
"location": "办公室",
"scene_type": "daily",
"source_text": "李四抬头点了点头,王五放下手中的笔"
},
{
"panel_number": 4,
"description": "中景:王五从座位上站起身,左侧李四也准备起身,张三在门口向外走去",
"characters": [
{"name": "王五", "appearance": "初始形象"},
{"name": "李四", "appearance": "初始形象"},
{"name": "张三", "appearance": "初始形象"}
],
"location": "办公室",
"scene_type": "daily",
"source_text": "王五站起身"
}
]
注意示例中的镜头连续性技巧:
- 每个镜头都交代了三个角色的位置
- 镜头焦点变化时(如镜头3焦点是李四王五),仍用「背景中张三」保持连续性
- 角色移动(如镜头4张三向外走)有明确动作交代
【输入数据】
角色资产库:{characters_lib_name}
场景资产库:{locations_lib_name}
角色介绍(⭐用于理解"我"和称呼对应的角色):
{characters_introduction}
角色形象列表(供选择appearance):
{characters_appearance_list}
角色完整描述(供参考):
{characters_full_description}
Clip信息:
{clip_json}
内容输入(剧本格式JSON或原文片段):
{clip_content}
【严格要求】
1. 必须输出所需数量的有效分镜,禁止空分镜
2. 角色和场景名字必须从资产库选择
3. characters 必须是对象数组:[{name: "角色名", appearance: "形象名"}]
4. 只返回JSON数组,不得有其他文字
5. ⚠️ source_text 必填,不得为空或null
6. 空间关系必须清晰(朝向、阻挡、位置)
7. 镜头连续性:前后镜头要有动作承接
8. 禁止身份称呼:必须使用资产库中的具体名字
9. 禁止主观情绪词:只描述可视化动作和状态
10. 禁止长句单镜头:包含逗号分隔多个动作/对话的长句必须拆分
11. 对话必须拆分:每段对话至少 2 个镜头(说话者 + 听者反应)
12. ⚠️ 镜头合理性:只描述当前镜头**实际能拍摄到**的角色,特写/反打等拍不到的可省略
13. ⚠️ JSON安全:原文中的所有引号(""''「」等)在 JSON 字符串值中必须统一替换为「」,严禁出现未转义的英文双引号 "
================================================
FILE: lib/prompts/novel-promotion/character_create.en.txt
================================================
You are a professional character prompt designer.
Generate one image-ready character appearance prompt from the user's request.
User request:
{user_input}
Requirements:
1. Output one complete English appearance prompt.
2. Include age range, facial traits, hairstyle, body build, outfit, shoes, and accessories.
3. Keep it visual and concrete, no story narration.
4. Do not include expression, action, background, or camera language.
5. Do not mention skin color, eye color, or lip color.
6. Keep it concise and production-ready.
Output format:
Return JSON only. ⚠️ JSON SAFETY: All quotation marks in text MUST be converted to corner brackets「」in JSON string values. NEVER use raw ASCII double quotes " inside string values:
{
"prompt": "character appearance prompt"
}
================================================
FILE: lib/prompts/novel-promotion/character_create.zh.txt
================================================
请按照以下提示词规则执行用户的生成人物需求
【人物生成要求(用于出图,中文描述)】
1. 生成1条详细的中文外貌描述,供AI图片生成使用
2. 描述要求突出角色特色,有细节质感:
- 性别、年龄范围(写具体年龄区间,如"约二十五岁"、"四十至四十五岁"、"五十岁左右")
- 面部:脸型、五官特征(如高挺鼻梁、深邃眼窝、薄唇等具体特征)
- 眼睛:形状、大小(禁止描写眼睛颜色)
- 头发:颜色、长度、发型、发质(如蓬松卷发、挑染银灰、发尾微卷等)
- 体型:身高感、体态、肩宽、腰线等
- 皮肤:只描述质感和独特标记(如光滑/粗糙、雀斑、胎记、疤痕、纹身等),禁止描述肤色
- 服装:款式、材质、配色、细节(如机车皮夹克、破洞牛仔裤、金属拉链、刺绣图案等)
- 鞋子:款式、颜色、材质(如黑色马丁靴、白色帆布鞋、棕色皮质牛津鞋、红底高跟鞋、绣花布鞋等)
- 配饰:耳钉、项链、手表、戒指等突出个性的配饰
3. 【角色类型判断】
- 如果用户描述的是非人类角色(动物、神话生物、知名IP形象等),不受上述人类外貌模板限制
- 描述开头必须以角色名或物种名开始
- 根据实际形态自由描述,保持核心辨识特征
示例:
- 输入"孙悟空" → 输出:"孙悟空,金毛覆身,头戴紧箍咒,身穿虎皮战裙,手持金箍棒..."
- 输入"一只蜗牛" → 输出:"蜗牛,背负螺旋形硬壳,两只触角细长探出..."
- 输入"皮卡丘" → 输出:"皮卡丘,黄色圆润身体,红色脸颊,闪电形尾巴,尖耳带黑色耳尖..."
4. 描述规范:
- 禁止写表情、姿态、动作
- 禁止写背景/环境/道具
- 不得加入情绪形容词与故事性句子
- 【禁止身体颜色描述】禁止描写任何身体部位的颜色,AI会过度放大颜色描述导致效果失真:
❌ 皮肤颜色(如偏黄、白皙、小麦色、古铜色、黝黑等)
❌ 唇色(如红润、粉色、苍白等)
❌ 眼睛颜色(如黑色、棕色、蓝色瞳孔等)
❌ 脸色(如红润、苍白、蜡黄等)
✅ 可以描写:皮肤质感(光滑/粗糙)、独特标记(雀斑/疤痕/纹身)、头发颜色、服装颜色
- 如原文对外貌有描述,以原文为最优先(但颜色描述仍需过滤)
- 使用中文输出,长度 80-150 字
- 不包含艺术风格、画风、光影效果描述(系统自动添加)
- 【年代一致性】根据故事背景判断年代(古代/近代/现代/未来等),人物的服装、发型、配饰必须符合该年代特征
- 【禁止不确定描述】禁止使用"或"、"可能"、"也许"、"大概"等不确定词汇,每个外貌特征必须明确具体
❌ 错误:"戴着无框或金框眼镜"、"身高可能一米七左右"
✅ 正确:"戴着金色细框眼镜"、"身材高挑约一米七五"
- 【禁止抽象气质描述】禁止描述无法视觉化的抽象气质、氛围、神态、感受
❌ 错误:"举手投足间透着富贵气"、"气场强大"、"成熟稳重的气息"
✅ 正确:只描述可直接看到的外貌特征
- 【鞋子必填】每个完整人物描述必须包含鞋子描述,不可遗漏
你的目标是根据用户发送你的需求以及上述规则生成一个人物提示词
以下是用户的生成指令:{user_input}
发送json格式给我,只返回以下json格式,禁止返回一切除json以外的多余内容,注释,文字等等,只返回无任何markdown标识符的纯净json格式。⚠️ 所有引号(""''等)在 JSON 字符串值中必须统一替换为「」,严禁出现未转义的英文双引号 "。json格式如下
{
"prompt":"xxxxx"
}
================================================
FILE: lib/prompts/novel-promotion/character_description_update.en.txt
================================================
You are a character appearance prompt editor.
Update the original character description according to the user's edit instruction.
Original description:
{original_description}
User instruction:
{modify_instruction}
Reference image context (may be empty):
{image_context}
Rules:
1. Keep unchanged traits unless user explicitly asks to change them.
2. Merge requested changes into a single complete prompt.
3. Output in English only.
4. No expression, action, background, or props.
5. No skin color, eye color, or lip color.
Output format:
Return JSON only. ⚠️ JSON SAFETY: All quotation marks MUST be converted to corner brackets「」in JSON string values:
{
"prompt": "updated full character description"
}
================================================
FILE: lib/prompts/novel-promotion/character_description_update.zh.txt
================================================
你是一个专业的角色形象描述更新专家。
【任务】
根据用户对角色图片的修改,更新角色的形象描述词。
【原始角色描述】
{original_description}
【用户修改指令】
{modify_instruction}
{image_context}
【更新规则】
1. 仔细理解用户的修改指令,找出需要修改的具体特征
2. 如果有参考图片,请识别参考图片中的关键视觉特征(如服装款式、颜色、材质、配饰等)
3. 将修改内容准确融入原始描述中,替换或补充相关部分
4. 保持描述的流畅性和一致性
5. 保留未被修改的原有特征
6. 遵循以下描述规范:
- 禁止写表情、姿态、动作
- 禁止写背景/环境/道具
- 禁止描写身体部位颜色(皮肤色、唇色、眼睛颜色等)
- 使用中文输出,长度 80-150 字
【输出格式】
只返回JSON格式,禁止返回任何其他内容。⚠️ 所有引号(""''等)在 JSON 字符串值中必须替换为「」,严禁出现未转义的英文双引号 ":
{
"prompt": "更新后的完整角色描述"
}
================================================
FILE: lib/prompts/novel-promotion/character_modify.en.txt
================================================
You are a professional character prompt modifier.
Modify an existing character description based on user instruction.
Current description:
{character_input}
User instruction:
{user_input}
Rules:
1. Keep identity consistency.
2. Apply only requested edits, keep other valid details.
3. Return one complete rewritten prompt, not partial fragments.
4. Output in English only.
5. Do not describe expression, action, background, scene, or props.
6. Do not mention skin color, eye color, or lip color.
Output format:
Return JSON only. ⚠️ JSON SAFETY: All quotation marks MUST be converted to corner brackets「」in JSON string values:
{
"prompt": "modified character description"
}
================================================
FILE: lib/prompts/novel-promotion/character_modify.zh.txt
================================================
请按照以下规则执行用户的人物生成提示词修改需求
1:人物规则按照以下规则修改
【人物生成要求(用于出图,中文描述)】
1. 生成1条详细的中文外貌描述,供AI图片生成使用
2. 描述要求突出角色特色,有细节质感:
- 性别、年龄范围(写具体年龄区间,如"约二十五岁"、"四十至四十五岁"、"五十岁左右")
- 面部:脸型、五官特征(如高挺鼻梁、深邃眼窝、薄唇等具体特征)
- 眼睛:形状、大小(禁止描写眼睛颜色)
- 头发:颜色、长度、发型、发质(如蓬松卷发、挑染银灰、发尾微卷等)
- 体型:身高感、体态、肩宽、腰线等
- 皮肤:只描述质感和独特标记(如光滑/粗糙、雀斑、胎记、疤痕、纹身等),禁止描述肤色
- 服装:款式、材质、配色、细节(如机车皮夹克、破洞牛仔裤、金属拉链、刺绣图案等)
- 鞋子:款式、颜色、材质(如黑色马丁靴、白色帆布鞋、棕色皮质牛津鞋、红底高跟鞋、绣花布鞋等)
- 配饰:耳钉、项链、手表、戒指等突出个性的配饰
3. 描述规范:
- 禁止写表情、姿态、动作
- 禁止写背景/环境/道具
- 不得加入情绪形容词与故事性句子
- 【禁止身体颜色描述】禁止描写任何身体部位的颜色,AI会过度放大颜色描述导致效果失真:
❌ 皮肤颜色(如偏黄、白皙、小麦色、古铜色、黝黑等)
❌ 唇色(如红润、粉色、苍白等)
❌ 眼睛颜色(如黑色、棕色、蓝色瞳孔、琥珀色等)
❌ 脸色(如红润、苍白、蜡黄等)
✅ 可以描写:皮肤质感(光滑/粗糙)、独特标记(雀斑/疤痕/纹身)、头发颜色、服装颜色
- 如原文对外貌有描述,以原文为最优先(但颜色描述仍需过滤)
- 使用中文输出,长度 80-150 字
- 不包含艺术风格、画风、光影效果描述(系统自动添加)
- 【年代一致性】根据故事背景判断年代(古代/近代/现代/未来等),人物的服装、发型、配饰必须符合该年代特征
- 【禁止不确定描述】禁止使用"或"、"可能"、"也许"、"大概"等不确定词汇,每个外貌特征必须明确具体
❌ 错误:"戴着无框或金框眼镜"、"身高可能一米七左右"
✅ 正确:"戴着金色细框眼镜"、"身材高挑约一米七五"
- 【禁止抽象气质描述】禁止描述无法视觉化的抽象气质、氛围、神态、感受
❌ 错误:"举手投足间透着富贵气"、"气场强大"、"成熟稳重的气息"
✅ 正确:只描述可直接看到的外貌特征
- 【鞋子必填】每个完整人物描述必须包含鞋子描述,不可遗漏
- 【非人类角色处理】如果当前角色是非人类(动物、神话生物、知名形象等):
* 不受人类外貌模板限制,根据实际形态描述
* 描述开头保持角色名或物种名
* 示例:修改"孙悟空"的服装 → "孙悟空,金毛蓬松,换上白色僧袍,头戴紧箍咒..."
2:你的目标是根据用户发送你的需求将人物修改为符合用户提示词的样子
以下是原本的人物生成提示词:{character_input}
以下是用户的修改指令:{user_input}
修改后发送json格式给我,只返回以下json格式,禁止返回一切除json以外的多余内容,注释,文字等等,只返回无任何markdown标识符的纯净json格式。⚠️ 所有引号(""''等)在 JSON 字符串值中必须统一替换为「」,严禁出现未转义的英文双引号 "。json格式如下
{
"prompt":"xxxxx"
}
================================================
FILE: lib/prompts/novel-promotion/character_regenerate.en.txt
================================================
You are a character appearance regenerator.
Generate 3 new character appearance variants based on story context.
Character name:
{character_name}
Appearance type / reason:
{change_reason}
Current descriptions (reference only, do not copy directly):
{current_descriptions}
Story context:
{novel_text}
Requirements:
1. Produce 3 clearly different variants while preserving core identity.
2. Each variant must be a full standalone appearance description.
3. Output in English.
4. Do not include expression, action, background, or story narration.
5. Do not include skin color, eye color, or lip color.
6. Keep each description concise and image-generation friendly.
Output format:
Return JSON only. ⚠️ JSON SAFETY: All quotation marks MUST be converted to corner brackets「」in JSON string values:
{
"descriptions": [
"variant 1",
"variant 2",
"variant 3"
]
}
================================================
FILE: lib/prompts/novel-promotion/character_regenerate.zh.txt
================================================
你是"角色形象重塑师"。请根据小说原文,为指定角色重新生成 3 条全新的外貌描述。
【角色信息】
- 角色名:{character_name}
- 形象类型:{change_reason}
- 当前描述(参考,需要生成不同的):
{current_descriptions}
【生成要求】
1. 根据小说原文对该角色的描写,生成 3 条各有特色、互不相同的外貌描述
2. 必须与当前描述有明显差异(换一种风格/配色/细节),但保持角色核心特征
3. ⚠️ 【重要】每条描述都必须是【完整的人物描述】,包含所有基础特征(面部、眼睛、体型等)+ 服装/状态,禁止只写变化部分
4. 【非人类角色处理】
- 如果角色是非人类(动物、神话生物、知名形象等),不受人类模板限制
- 每条描述开头必须以角色名或物种名开始
- 根据实际形态自由描述外观特征
5. 描述内容:
- 性别、年龄段(不写具体数字)
- 面部:脸型、五官特征(如高挺鼻梁、深邃眼窝、薄唇等)
- 眼睛:形状、大小(禁止描写眼睛颜色)
- 头发:颜色、长度、发型、发质(如蓬松卷发、挑染银灰等)
- 体型:身高感、体态、肩宽、腰线等
- 皮肤:只描述质感和独特标记(如光滑/粗糙、雀斑、疤痕、纹身等),禁止描述肤色
- 服装:款式、材质、配色、细节(如机车皮夹克、金属拉链等)
- 鞋子:款式、颜色、材质(如黑色马丁靴、白色帆布鞋、棕色皮质牛津鞋、红底高跟鞋、绣花布鞋等)
- 配饰:耳钉、项链、手表、戒指等突出个性的配饰
4. 描述规范:
- 禁止写表情、姿态、动作
- 禁止写背景/环境/道具
- 不得加入情绪形容词与故事性句子
- 【禁止身体颜色描述】禁止描写任何身体部位的颜色,AI会过度放大颜色描述导致效果失真:
❌ 皮肤颜色(如偏黄、白皙、小麦色、古铜色、黝黑等)
❌ 唇色(如红润、粉色、苍白等)
❌ 眼睛颜色(如黑色、棕色、蓝色瞳孔、琥珀色等)
❌ 脸色(如红润、苍白、蜡黄等)
✅ 可以描写:皮肤质感(光滑/粗糙)、独特标记(雀斑/疤痕/纹身)、头发颜色、服装颜色
- 如原文对外貌有描述,以原文为最优先(但颜色描述仍需过滤)
- 使用中文输出,长度 80-150 字
- 不包含艺术风格、画风、光影效果描述(系统自动添加)
- 【年代一致性】根据故事背景判断年代,服装、发型、配饰必须符合该年代特征
【输出格式】只返回以下 JSON,不要任何其他内容。⚠️ 所有引号(""''等)在 JSON 字符串值中必须替换为「」,严禁出现未转义的英文双引号 "。
{
"descriptions": [
"新描述1(80-150字)",
"新描述2(80-150字)",
"新描述3(80-150字)"
]
}
【小说原文】
{novel_text}
================================================
FILE: lib/prompts/novel-promotion/episode_split.en.txt
================================================
You are a long-text episode splitter.
Analyze the full text and split it into balanced episodes.
Input text:
{CONTENT}
Core rules:
1. Keep episode lengths as balanced as possible.
2. Prefer natural breakpoints (chapter boundary, scene shift, time jump).
3. Keep chronology and full coverage (no overlap, no missing text).
4. Provide reliable startMarker and endMarker copied from source text.
5. Return JSON only.
6. ⚠️ JSON SAFETY: All quotation marks in text (""''「」 etc.) MUST be converted to corner brackets「」in JSON string values. NEVER use raw ASCII double quotes " inside string values.
Output format:
{
"analysis": {
"totalWords": 0,
"episodeCount": 0,
"targetWordsPerEpisode": 0,
"allowedRange": "0-0"
},
"episodes": [
{
"number": 1,
"title": "Episode title",
"summary": "Short summary",
"estimatedWords": 0,
"startMarker": "exact start marker",
"endMarker": "exact end marker",
"startIndex": 0,
"endIndex": 0
}
],
"validation": {
"maxWords": 0,
"minWords": 0,
"variance": 0,
"isBalanced": true
}
}
================================================
FILE: lib/prompts/novel-promotion/episode_split.zh.txt
================================================
你是一个专业的内容分析助手。请分析以下文本,将其智能分割为多个剧集。
## ⚠️ 核心规则:字数必须均衡(最重要)
**所有剧集的字数必须尽可能均衡!偏差不得超过 ±20%**
### 📊 第一步:精确计算(必须执行)
1. 统计总字数:count_total_characters(文本)
2. 计算目标集数:total ÷ 650 = N 集(四舍五入)
3. 计算每集目标字数:target = total ÷ N
4. 确定允许范围:[target × 0.8, target × 1.2]
**示例**:
- 总字数 6500 字 → 10 集 → 每集目标 650 字 → 范围 520-780 字
- 总字数 13000 字 → 20 集 → 每集目标 650 字 → 范围 520-780 字
### 📐 第二步:均衡分割(必须执行)
❌ **绝对禁止**:
- 任何一集超过 target × 1.3(如目标650字,禁止超过845字)
- 任何一集少于 target × 0.6(如目标650字,禁止少于390字)
- 前几集很长、后几集很短(或反过来)
✅ **必须保证**:
- 所有集的字数在目标值 ±20% 范围内
- 最长集与最短集的差距不超过 300 字
- 字数分布均匀,不能头重脚轻或头轻脚重
### 🎬 第三步:寻找分割点
1. **优先识别自然断点**:
- 章节标记:「第X集」「Chapter X」「Episode X」
- 场景编号:`X-Y【场景】` 中的 X 变化(1-x → 2-x 是新集)
- 时间跳跃:「第二天」「三个月后」
2. **在自然断点附近微调**:
- 如果自然断点导致字数不均,可在附近段落边界调整
- 优先在对话结束、场景转换处分割
- 宁可牺牲一点叙事连贯性,也要保证字数均衡
## 输入文本
{{CONTENT}}
## 📝 输出格式
```json
{
"analysis": {
"totalWords": 6500,
"episodeCount": 10,
"targetWordsPerEpisode": 650,
"allowedRange": "520-780"
},
"episodes": [
{
"number": 1,
"title": "剧集标题(4-8字)",
"summary": "50字以内的剧情简介",
"estimatedWords": 650,
"startMarker": "该集开头的前20个字符(精确复制原文)",
"endMarker": "该集结尾的后20个字符(精确复制原文)"
}
],
"validation": {
"maxWords": 720,
"minWords": 590,
"variance": 130,
"isBalanced": true
}
}
```
## ⚠️ 最终验证清单(输出前必须检查)
在输出之前,你必须验证以下条件:
1. ☐ episodes.length ≈ totalWords ÷ 650(误差 ±1 集)
2. ☐ 所有 estimatedWords 都在 allowedRange 范围内
3. ☐ maxWords - minWords ≤ 300 字
4. ☐ 没有任何一集超过 850 字
5. ☐ 没有任何一集少于 400 字
6. ☐ 上一集 endMarker 紧邻下一集 startMarker,无内容遗漏
7. ☐ endMarker 不包含下一集的任何内容
**如果验证失败,必须重新调整分割点直到通过!**
⚠️ JSON安全:所有引号(""''等)在 JSON 字符串值中必须统一替换为「」,严禁出现未转义的英文双引号 "。只返回JSON,禁止markdown代码块标记。
## 🔧 场景编号说明
- `X-Y【场景描述】` 格式中,X = 集数,Y = 场景序号
- 1-1, 1-2, 1-3 都属于第 1 集
- 2-1 开始第 2 集
- 分集在 X 变化时进行
================================================
FILE: lib/prompts/novel-promotion/image_prompt_modify.en.txt
================================================
You are a prompt refinement expert for storyboard image/video generation.
Current image prompt:
{prompt_input}
Current video prompt:
{video_prompt_input}
User instruction:
{user_input}
Requirements:
1. Return an updated image prompt and video prompt.
2. Keep subject identity and scene continuity unless user asks to change.
3. Write in concise English.
4. Image prompt should focus on visual composition.
5. Video prompt should focus on motion/performance/camera behavior.
Output format:
Return JSON only. ⚠️ JSON SAFETY: All quotation marks MUST be converted to corner brackets「」in JSON string values:
{
"image_prompt": "updated image prompt",
"video_prompt": "updated video prompt"
}
================================================
FILE: lib/prompts/novel-promotion/image_prompt_modify.zh.txt
================================================
请按照以下提示词规则执行用户的ai生图以及视频运动提示词场景需求
1. 详细描述镜头角度和地点、动作、人物、环境,也就是谁、和谁、在哪里、干了什么,如果是空镜标题等,那么也要描述出原文想要表达的东西,例如作者名字,或者和原文有关的内容,提示词务必详细完整
2. **场景描述使用规则(重要):**
- locations字段:只写场景名字(如"病房_白天")
- image_prompt字段:直接使用场景名字(如"病房_白天"),可以结合具体位置描述(如"病房_白天的窗边")
- **禁止添加光线、色调、天气描述**:场景资产已包含完整的光线和色调信息,提示词中不要添加"阳光"、"光线"、"明亮"、"昏暗"、"温暖色调"、"冷色调"、"天气"等描述
- **人物所在的位置和互动的地方要和场景提示词中已有的物品有关系,不要出现人物和场景没有的关系进行互动**
3. **人物描述使用规则:**
- characters字段:只写人物名字(如"Victor")
- image_prompt字段:直接使用人物名字(如"Victor"),可以结合动作和情绪描述(如"Victor站在门口,表情严肃")
- 写图片生成提示词的时候务必要写明地点+人物名字+情绪+动作
- 我们这个是有声书第一视角,第一视角默认就是主角的视角,也就是在pov中的人物决策,如无其他意外那么这个就是主角,按照这个名字来进行主视角生成
4. 我们的场景限制只对有效目前人物处在实际发生地点的有效!例如有回忆、旁白等情况,我们不应该完全限制在固定场景之内,而是可以插入多样性的画面
5. 使用中文输出提示词,提示词编写规范按照用简洁连贯的自然语言写明:主体 + 行为 + 环境 + 空间关系
例如:Dr.Smith站在病床边,看着躺在床上的Mary,病房_白天
6.涉及到文字内容的全部使用原文srt的语言作为输出,如原文srt是英文,就代表着我们这个面向英文观众,那么如果会画面内容里面出现文字的话(例如出现了医院名字的特写)就要输出英文的具体文字提示词(注意,主要提示词还是中文),如:医院的镜头特写,上面写着hospital
7.使用主体+行为+环境的提示词,确保不要错过主体,除非无主体的空镜,否则一定要交代主体是谁
按照用户的修改指令把提示词修改为用户需要的样子,用户可能会发送一些额外内容让你修改,例如让你把人物修改为另外一个人物,这个时候你会看到人物或场景的具体描述词
例如用户输入:把张三(黑色长发,穿着西装...)修改为小明(蓝色头发,红色眼睛...)然后就可以利用这些具体描述词来按照以上的生成提示词规则修改。
当前的图片提示词:{prompt_input}
当前的视频提示词:{video_prompt_input}
用户的修改指令:{user_input}
结果发送json格式给我,只返回以下json格式,禁止返回一切除json以外的多余内容,注释,文字等等,只返回无任何markdown标识符的纯净json格式。⚠️ 所有引号(""''等)在 JSON 字符串值中必须统一替换为「」,严禁出现未转义的英文双引号 "。json格式如下
{
"image_prompt": "修改后的图片提示词(静态画面描述)",
"video_prompt": ""
}
================================================
FILE: lib/prompts/novel-promotion/location_create.en.txt
================================================
You are a professional environment prompt designer.
Generate one scene prompt for image generation.
User request:
{user_input}
Rules:
1. Output in English only.
2. Start with scene name in this format: "[Scene Name] ..."
3. Describe a wide, clear environment with spatial layout and key objects.
4. Mention lighting direction and atmosphere.
5. No protagonist actions or dialogue.
6. If crowd is implied by context, use generic crowd terms only (guests, pedestrians, audience).
Output format:
Return JSON only. ⚠️ JSON SAFETY: All quotation marks MUST be converted to corner brackets「」in JSON string values:
{
"prompt": "[Scene Name] environment description"
}
================================================
FILE: lib/prompts/novel-promotion/location_create.zh.txt
================================================
请按照以下提示词规则执行用户的生成场景需求
【场景生成要求(用于出图,中文描述)】
1. 生成1条中文环境描述(60-120字),像真实摄影场景一样描述
2. **开头必须明确写明场景名称**:
- 描述开头必须以"【场景名称】"的形式标注空间属性
- 示例:「皇宫」殿内铺设着... / 「客厅」窗外阳光透过... / 「卧室」床边放着...
- 这样AI在生成图片时能明确理解这是什么类型的空间
3. 核心原则:
- 写真实存在的物体,不要写抽象感受
- 材质要具体(深棕色实木地板、青灰色石砖墙、做旧铁艺栏杆)
- 物品要有使用痕迹和生活气息(桌上散落的书籍、墙角堆放的杂物、窗台晒干的植物)
- 光线要写清楚来源和效果(午后阳光斜照进来在地板上拉出长影、暖黄色壁灯打在墙面上)
4. 禁止:不写主角人物具体动作、不写画风、不写"温馨""优雅"等抽象词
5. 【人群处理规则】
- 如果用户描述的场景暗示有人群(如宴会厅、集市、教室上课等),在描述中加入模糊人群元素
- 人群描述示例:"大厅中宾客三两成群"、"街道上行人往来"、"座位上零散坐着几位观众"
- 如果是私密空间或用户明确要求空镜,则不添加人群
以下是用户的生成指令:{user_input}
只返回以下json格式,禁止返回一切除json以外的多余内容。⚠️ 所有引号(""''等)在 JSON 字符串值中必须替换为「」,严禁出现未转义的英文双引号 "。
{
"prompt":"「场景名称」场景描述内容"
}
================================================
FILE: lib/prompts/novel-promotion/location_description_update.en.txt
================================================
You are a scene description editor.
Update the original location description based on user instruction.
Location name:
{location_name}
Original description:
{original_description}
User instruction:
{modify_instruction}
Reference image context (may be empty):
{image_context}
Rules:
1. Keep unchanged scene elements unless explicitly modified.
2. Return one complete updated description in English.
3. Keep scene name at the beginning: "[{location_name}] ..."
4. No protagonist actions or story narration.
Output format:
Return JSON only. ⚠️ JSON SAFETY: All quotation marks MUST be converted to corner brackets「」in JSON string values:
{
"prompt": "updated location description"
}
================================================
FILE: lib/prompts/novel-promotion/location_description_update.zh.txt
================================================
你是一个专业的场景描述更新专家。
【任务】
根据用户对场景图片的修改,更新场景的描述词。
【场景名称】
{location_name}
【原始场景描述】
{original_description}
【用户修改指令】
{modify_instruction}
{image_context}
【更新规则】
1. **开头必须明确写明场景名称**:
- 描述开头必须以「{location_name}」的形式标注空间属性
- 示例:「皇宫」殿内铺设着... / 「客厅」窗外阳光透过...
- 这样AI在生成图片时能明确理解这是什么类型的空间
2. 仔细理解用户的修改指令,找出需要修改的具体特征
3. 如果有参考图片,请识别参考图片中的关键视觉特征(如建筑风格、装饰元素、光线氛围、色调等)
4. 将修改内容准确融入原始描述中,替换或补充相关部分
5. 保持描述的流畅性和一致性
6. 保留未被修改的原有特征
7. 遵循以下描述规范:
- 只描述场景本身,禁止描述人物
- 使用中文输出,长度 50-100 字
【输出格式】
只返回JSON格式,禁止返回任何其他内容。⚠️ 所有引号(""''等)在 JSON 字符串值中必须替换为「」,严禁出现未转义的英文双引号 ":
{
"prompt": "「场景名」更新后的完整场景描述"
}
================================================
FILE: lib/prompts/novel-promotion/location_modify.en.txt
================================================
You are a professional scene prompt modifier.
Modify an existing scene description while preserving scene identity.
Location name:
{location_name}
Current description:
{location_input}
User instruction:
{user_input}
Rules:
1. Keep core scene identity and function.
2. Apply requested changes with concrete visual details.
3. Output in English only.
4. Start with scene name: "[{location_name}] ..."
5. No protagonist actions, dialogue, or narrative plot.
Output format:
Return JSON only. ⚠️ JSON SAFETY: All quotation marks MUST be converted to corner brackets「」in JSON string values:
{
"prompt": "modified location description"
}
================================================
FILE: lib/prompts/novel-promotion/location_modify.zh.txt
================================================
请按照以下提示词规则执行用户的修改场景需求
【场景名称】
{location_name}
【场景生成要求(用于出图,中文描述)】
1. **开头必须明确写明场景名称**:
- 描述开头必须以「{location_name}」的形式标注空间属性
- 示例:「皇宫」殿内铺设着... / 「客厅」窗外阳光透过...
- 这样AI在生成图片时能明确理解这是什么类型的空间
2. 每个场景生成 1 条中文环境描述,用于AI图片生成
3. 必须包含具体的视觉元素,按以下结构描述:
**空间定位**:明确场景类型
- 室内:客厅/卧室/厨房/办公室/医院病房/教室等
- 室外:街道/巷子/公园/山谷/海边/广场等
**主要结构**:描述可见的建筑元素
- 墙面/地面/天花板的材质和颜色(如"白色墙面"/"木质地板"/"水泥地面"/"瓷砖地面")
- 门窗位置和类型(如"左侧有大窗户"/"右侧木门"/"落地窗"/"玻璃门")
- 建筑特征(如"高天花板"/"拱形门"/"砖墙"/"玻璃幕墙")
**家具/道具**:列出场景中的主要物体
- 家具摆放(如"中央有沙发"/"墙边书架"/"床靠窗"/"办公桌")
- 特征道具(如"桌上有台灯"/"墙上挂画"/"地上地毯"/"书架上有书")
- 植物装饰(如"窗台有盆栽"/"角落有绿植")
**光线环境**:描述光源和照明效果
- 自然光:时间段+光线特征
* 白天:窗外阳光透过窗帘/明亮日光从窗户照入/柔和晨光
* 夜晚:月光从窗外照入/窗外夜色/窗外灯光
- 人工光:光源类型+位置
* 天花板吊灯照明/墙壁壁灯柔和光线/台灯局部照明/落地灯/射灯
**氛围细节**:补充环境特征
- 整体色调:暖色调/冷色调/中性色/明亮/昏暗
- 空间感:宽敞/狭窄/纵深感强/开阔/封闭
- 状态特征:整洁/凌乱/陈旧/现代/简约/复古
4. 描述规范:
- 使用具体名词,避免抽象形容词
* ✅ 好:"木质书桌"/"灰色布艺沙发"/"白色窗帘"
* ❌ 差:"优雅的家具"/"舒适的环境"/"温馨的氛围"
- 描述固定元素,不写主角人物具体动作、情绪
- 【人群处理规则】如果用户要求添加人群,或场景本身暗示有人群(如宴会、集市等):
* 可以加入模糊人群描述:\"人群\"、\"宾客\"、\"路人\"等
* 示例:\"大厅远处三两宾客交谈\"、\"街角有行人匆匆走过\"
- 长度控制在 60-100 字
- 不包含艺术风格描述(如"美式漫画风"/"水彩风"),风格由系统自动添加
- 不包含光影效果描述(如"dramatic lighting"/"柔和渐变"),由风格控制
你的目标是根据用户的修改指令,在原有场景描述的基础上进行修改
当前场景描述:{location_input}
用户的修改指令:{user_input}
发送json格式给我,只返回以下json格式,禁止返回一切除json以外的多余内容,注释,文字等等,只返回无任何markdown标识符的纯净json格式。⚠️ 所有引号(""''等)在 JSON 字符串值中必须统一替换为「」,严禁出现未转义的英文双引号 "。json格式如下
{
"prompt":"「场景名」xxxxx"
}
================================================
FILE: lib/prompts/novel-promotion/location_regenerate.en.txt
================================================
You are a scene variant regenerator.
Generate 3 new scene description variants for the same location.
Location name:
{location_name}
Current descriptions (reference):
{current_descriptions}
Requirements:
1. Generate 3 clearly different but same-location variants.
2. Keep the scene name prefix in each line: "[{location_name}] ..."
3. Output in English only.
4. Keep environment-only description (no protagonist actions).
5. Keep each variant concise and image-generation friendly.
Output format:
Return JSON only. ⚠️ JSON SAFETY: All quotation marks MUST be converted to corner brackets「」in JSON string values:
{
"descriptions": [
"[{location_name}] variant 1",
"[{location_name}] variant 2",
"[{location_name}] variant 3"
]
}
================================================
FILE: lib/prompts/novel-promotion/location_regenerate.zh.txt
================================================
你是"场景重塑师"。请根据当前的场景描述,为指定场景重新生成 3 条全新的场景描述变体。
【场景信息】
- 场景名:{location_name}
- 当前描述(作为参考,需要生成不同的变体):
{current_descriptions}
【生成要求】
1. **开头必须明确写明场景名称**:
- 每条描述开头必须以「{location_name}」的形式标注空间属性
- 示例:「皇宫」殿内铺设着... / 「客厅」窗外阳光透过...
- 这样AI在生成图片时能明确理解这是什么类型的空间
2. 根据当前描述的核心元素,生成 3 条各有特色、互不相同的场景描述变体
3. 必须与当前描述有明显差异(换一种氛围/布局/细节),但保持场景核心特征
4. 描述内容应包含:
- 空间结构:整体布局、空间大小、层次感
- 建筑/地形:建筑风格、地形特征、主要构造物
- 光影氛围:光源类型、明暗对比、色调倾向
- 材质细节:地面、墙面、物体的材质质感
- 环境元素:植物、天气、装饰物等
- 独特标识:该场景的标志性元素或特殊物件
5. 描述规范:
- 禁止写主角人物具体动作、剧情
- 【人群处理规则】如果当前描述中包含人群元素,新描述也应保持人群元素
* 可以调整人群的位置、密度、状态,但保持有人群存在
* 人群描述使用模糊词汇:"人群"、"宾客"、"路人"等
- 使用中文输出,长度 80-150 字
- 不包含艺术风格、画风描述(系统自动添加)
- 【年代一致性】根据场景特征判断年代,建筑、装饰、物品必须符合该年代特征
- 【时间一致性】如场景名包含"白天/黑夜/黄昏"等,描述中的光影必须匹配
【输出格式】只返回以下 JSON,不要任何其他内容。⚠️ 所有引号(""''等)在 JSON 字符串值中必须替换为「」,严禁出现未转义的英文双引号 "。
{
"descriptions": [
"「场景名」新描述1(80-150字)",
"「场景名」新描述2(80-150字)",
"「场景名」新描述3(80-150字)"
]
}
================================================
FILE: lib/prompts/novel-promotion/screenplay_conversion.en.txt
================================================
You are a screenplay conversion specialist.
Convert the clip text into structured screenplay JSON without adding new story facts.
Clip ID:
{clip_id}
Clip content:
{clip_content}
Location library:
{locations_lib_name}
Character library:
{characters_lib_name}
Character introductions:
{characters_introduction}
Output format (JSON object only):
{
"clip_id": "{clip_id}",
"original_text": "original clip text",
"scenes": [
{
"scene_number": 1,
"heading": {
"int_ext": "INT or EXT",
"location": "location name",
"time": "morning/day/evening/night"
},
"description": "scene setup",
"characters": ["Character A", "Character B"],
"content": [
{
"type": "action",
"text": "action description"
},
{
"type": "dialogue",
"character": "Character A",
"parenthetical": "optional performance cue",
"lines": "spoken line"
},
{
"type": "voiceover",
"character": "Narrator or Character",
"text": "voiceover content"
}
]
}
]
}
Rules:
1. Preserve original story facts; do not invent new events.
2. Keep scene/content order aligned with source text.
3. Resolve character aliases to canonical names when possible.
4. Use the best matching location name from library; if none fits, use source location text.
5. Return strict JSON only.
6. ⚠️ JSON SAFETY: All quotation marks in dialogue (""''「」 etc.) MUST be converted to corner brackets「」in JSON string values. NEVER use raw ASCII double quotes " inside string values—they break JSON structure.
================================================
FILE: lib/prompts/novel-promotion/screenplay_conversion.zh.txt
================================================
你是专业的编剧和剧本改编师。你的任务是将小说/文学文本转换为标准的影视剧本格式。
⚠️⚠️⚠️【最高优先级原则 - 100%忠实原文】⚠️⚠️⚠️
你的工作是**格式转换**,不是**创作**!你必须100%忠实于原文,严禁任何形式的"创造性发挥":
🚫 绝对禁止:
- 添加原文中没有的对话、动作、场景描述
- 扩展或改编原文内容(即使你觉得"更合理"或"更生动")
- 添加原文没有提及的角色反应、表情、心理活动
- 脑补原文没有描写的环境细节、道具、氛围
- 添加过渡性描述来"填充空白"
- 用你的理解"补全"原文的留白
✅ 你只能做:
- 将原文已有的内容转换为剧本格式
- 识别原文明确描述的场景、角色、对话
- 提取原文已有的动作和环境描述
如果原文内容简短或留白,你的剧本也应该简短,不要试图"丰富"它!
【核心职责】
1. 把小说文字转换为剧本格式(场景、对话、动作描述)
2. 提取场景的时间、地点、环境信息
3. 识别场景中出现的角色(匹配资产库)
4. 区分动作、对话、画外音等不同类型
5. ⭐100%保持原文信息的完整性,不增不减
【剧本格式规范】
标准剧本包含以下元素:
1. **场景头(Scene Heading)**:
- 格式: 内景/外景 + 地点 + 时间
- 示例: "内景 客厅 清晨" / "外景 取经路 白天"
2. **场景描述(Scene Description)**:
- 简洁描述场景环境、布局、关键道具
- 不需要过度细节,只需要建立基本视觉印象
3. **动作描述(Action)**:
- 描述角色的动作、表情、行为
- 连续的段落形式,不要拆分成碎片
4. **对话(Dialogue)**:
- 角色名字
- 副文本(parenthetical): 括号内的表演指导
- 台词内容
5. **画外音(Voiceover)**:
- 旁白、独白、回忆、读信等不在画面中的声音
- 标记角色(如果是特定角色的独白)
【转换规则】
## 1. 场景识别规则
- 分析原文,识别场景边界(地点变化、时间跨越)
- 每个场景必须包含:
* 内景(INT)或外景(EXT)
* 地点名称:
- 优先从场景资产库选择完全一致的名称
- ⚠️【重要】如果资产库中没有匹配的场景,直接使用原文中的场景名称,不要强行匹配错误的资产库场景
- 宁可输出资产库中不存在的场景名,也不要用错误的场景名(后续会自动创建缺失的资产)
* 时间段(清晨/上午/正午/下午/黄昏/夜晚/深夜)
## 2. 内容类型识别
必须准确区分以下类型:
**action** - 动作描述
- 角色的动作、表情、行为
- 场景变化、环境细节
- 示例: "孙悟空举起金箍棒,朝六耳猕猴砸去。"
**dialogue** - 对话
- 画面中角色的说话
- 必须包含:character(角色名)、lines(台词)
- 可选包含:parenthetical(副文本,如"愤怒地""小声")
- 示例:
角色: 孙悟空
副文本: 愤怒地
台词: "一个冒牌货,也敢拦你孙爷爷的路!"
**voiceover** - 画外音
- 旁白、独白、回忆中的声音、心理活动
- 不在画面中出现的声音
- 示例: "原来孙悟空真的死在了取经路上。" (旁白)
- 示例: 二郎神独白:"猴子死了,我却没有出手..." (特定角色的画外音)
## 3. 角色识别规则
- 优先从角色资产库中选择完全一致的名字
- ⚠️【重要】如果资产库中没有匹配的角色,直接使用原文中的角色名称,不要强行匹配错误的资产库角色
- 宁可输出资产库中不存在的角色名,也不要用错误的角色名(后续会自动创建缺失的资产)
- 不要使用简称:用"六耳猕猴"而非"六耳"
- 不要使用代词:用具体名字替代"他""她"
- characters数组只包含画面中出现的角色,不包括画外音角色
## 4. 副文本(Parenthetical)提取规则
从原文中识别并提取表演指导:
- "XX愤怒地说" → parenthetical: "愤怒地"
- "XX小声嘀咕" → parenthetical: "小声"
- "XX(转身)说" → parenthetical: "转身"
- "XX边走边说" → parenthetical: "边走边"
## 5. 动作连续性规则
- 动作描述应该是连续的段落,不要过度拆分
- 多个连续动作可以合并在一个action中
- 示例: ❌ 不好: "孙悟空站起来。" + "孙悟空走向门口。" + "孙悟空打开门。"
- 示例: ✅ 好: "孙悟空站起来,走向门口,打开门。"
【输出格式】
只返回JSON对象,不得有markdown代码块标记、注释或解释。
{
"clip_id": "{clip_id}",
"original_text": "原文内容",
"scenes": [
{
"scene_number": 1,
"heading": {
"int_ext": "INT或EXT",
"location": "场景名称(必须从资产库选择)",
"time": "时间段"
},
"description": "场景环境描述",
"characters": ["角色1", "角色2"],
"content": [
{
"type": "action",
"text": "动作描述文本"
},
{
"type": "dialogue",
"character": "角色名",
"parenthetical": "副文本",
"lines": "台词内容"
},
{
"type": "voiceover",
"character": "角色名或旁白",
"text": "画外音内容"
}
]
}
]
}
【场景描述撰写规则】
description字段应该简洁但信息丰富,包含:
1. 环境类型(室内/室外/特殊环境)
2. 主要布局(如"狭长的走廊""开阔的广场")
3. 关键道具或环境元素(如"右侧落地窗""远处山脉")
4. 氛围提示(如"荒凉""温馨""阴森")
示例:
- "简约客厅。米白色墙面,木质地板。右侧落地窗,左侧入口门。"
- "荒凉的取经路。黄沙漫天,远处是连绵的山脉。"
- "阴暗的洞穴。石壁潮湿,只有微弱的火光。"
【特殊情况处理】
1. **第一人称叙述**:
- 如果原文是"我走进房间",需要替换为具体角色名
- ⭐ 参考【角色介绍】中的说明,找到"我"对应的角色
- 如果角色介绍中说明"我"对应某角色,则使用该角色名
- 如果资产库有"我"这个角色,则使用"我"
2. **称呼映射**:
- ⭐ 参考【角色介绍】中的称呼说明
- 如"老公"在介绍中说明对应"林墨",则dialogue的character填"林墨"
- 不要被原文的称呼误导,以资产库的名字为准
3. **回忆/闪回场景**:
- 回忆中的对话/动作 → 正常处理(因为画面中会演)
- 回忆的旁白叙述 → 使用voiceover类型
4. **多个小场景**:
- 如果原文包含多个地点变化,拆分成多个scene
- 每个scene有独立的scene_number
5. **心理活动**:
- 角色的内心想法 → voiceover类型
- 标记character为对应角色
【严格要求 - 必须遵守】
⭐⭐⭐ 忠实原文的强制要求 ⭐⭐⭐
1. 🚨【最重要】禁止编造!所有动作、对话、描述必须来自原文,不能添加任何原文中没有的内容
2. 🚨 如果原文只有一句话,剧本也只能有一句话对应的内容,禁止"扩写"
3. 🚨 如果原文没有描述角色的表情/动作,禁止添加"XX露出微笑"、"XX点了点头"等内容
4. 🚨 如果原文没有环境描写,description字段只写能从原文推断的最基本信息
5. 🚨 禁止添加过渡性动作,如"XX走了过来"、"XX转身离开"(除非原文明确写了)
格式要求:
1. 只返回JSON对象,不得有markdown标记或注释
2. location优先从场景资产库选择;如果资产库没有匹配的,使用原文场景名称(宁可缺失也不用错误的)
3. characters优先从角色资产库选择;如果资产库没有匹配的,使用原文角色名称(宁可缺失也不用错误的)
4. ⭐ 根据角色介绍中的称呼映射,将原文中的"我"、"老公"等替换为正确的角色名
5. ❌ 严禁添加原文中没有的内容(这是最常见的错误!)
6. content数组保持时间顺序
7. type只能是: action, dialogue, voiceover
8. dialogue必须包含character和lines
9. voiceover如果是特定角色必须包含character
10. parenthetical是可选的,只在原文有明确表演指导时添加
11. 输出必须是**严格合法的JSON**:字符串中不能出现原始换行/回车/制表符,必须使用转义字符(\\\\n、\\\\r、\\\\t)
12. clip_id 必须与输入的 Clip ID 完全一致,严禁输出 "{clip_id}" 这种占位符
13. 建议输出为单行JSON对象(不包含多余空行/解释)
⚠️⚠️⚠️【JSON安全输出 - 最高优先级】⚠️⚠️⚠️
- 原文中的所有引号(""''「」『』等)在 JSON 字符串值中必须统一替换为日式方括号引号「」
- ❌ 严禁在 JSON 字符串值中出现英文双引号 " !会破坏 JSON 结构!
- ✅ 正确:"lines":"孙悟空怒道,「一个冒牌货,也敢拦你孙爷爷的路!」"
- ❌ 错误:"lines":"孙悟空怒道,"一个冒牌货,也敢拦你孙爷爷的路!""
- 如果字符串内确实需要英文双引号,必须用 \" 转义
- 这条规则适用于 lines、text、description、original_text 等所有字符串字段
⚠️ 自检清单(输出前必须确认):
- [ ] 我的每一句动作描述都能在原文中找到对应吗?
- [ ] 我的每一句对话都是原文的原话吗?
- [ ] 我有没有添加原文没有的"走过来"、"点头"、"微笑"等动作?
- [ ] 我有没有"丰富"原文简短的描写?
【输入数据】
Clip原文:
{clip_content}
场景资产库(优先匹配,无匹配时可使用原文名称):
{locations_lib_name}
角色资产库(优先匹配,无匹配时可使用原文名称):
{characters_lib_name}
角色介绍(⭐重要:用于理解"我"和称呼对应的角色以及角色关系):
{characters_introduction}
Clip ID:
{clip_id}
【输出要求】
请将上述原文转换为标准剧本格式,只返回JSON对象。
再次强调:输出必须是**严格合法的JSON**,不得包含任何额外文本。
================================================
FILE: lib/prompts/novel-promotion/select_location.en.txt
================================================
You are a location asset extraction specialist.
Extract locations that need dedicated background assets.
Input text:
{input}
Existing location library:
{locations_lib_name}
Selection rules:
1. Include locations where meaningful story actions happen.
2. Exclude abstract/metaphorical spaces and one-off passing mentions.
3. Deduplicate aliases of the same location.
4. Prefer exact library names when a location already exists.
For each selected location, generate 3 wide-angle environment descriptions.
Each description should:
- start with location name in brackets: "[Location Name] ..."
- describe spatial layout, depth layers, major objects, and lighting direction
- remain environment-only (no named protagonist actions)
- use concise, production-ready English
Output format (JSON only):
{
"locations": [
{
"name": "location_name",
"summary": "short usage summary",
"has_crowd": false,
"crowd_description": "",
"descriptions": [
"[location_name] description 1",
"[location_name] description 2",
"[location_name] description 3"
]
}
]
}
Strict constraints:
1. JSON only.
2. If no valid location exists, return: {"locations":[]}.
3. ⚠️ JSON SAFETY: All quotation marks in text (""''「」 etc.) MUST be converted to corner brackets「」in JSON string values. NEVER use raw ASCII double quotes " inside string values.
================================================
FILE: lib/prompts/novel-promotion/select_location.zh.txt
================================================
你是"场景资产建立师"。请基于我提供的文本(可能是小说、剧本、或混合格式),筛选【需要制作画面的场景】,生成用于出图与后续生产的资产 JSON。
【筛选规则 - 精准提取模式】
✅【必须提取的场景】:
- 剧本场景头部中出现的地点(如"内景 客厅 白天")
- 角色实际身处、产生互动的具体场所
- 剧情主线发生的核心地点
- 多次出现或戏份较重的场景
- 有明确空间描写、需要制作背景画面的地点
❌【不提取的场景】(严格执行!):
- 一次性路过、仅提及但无剧情发生的地点
- 意境类、比喻类、修辞类描述(如"从天堂打到地狱"、"从天上打到地下"、"心灵深处"、"记忆长河"等)
- 抽象空间或无法具象化的概念(如"命运交汇点"、"时空裂缝")
- 仅作为对话背景提及、没有实际画面需求的地点
- 纯过渡性场景(如"穿过走廊"、"路过门口"等一笔带过的移动描述)
- 回忆/幻想中一闪而过、没有具体剧情的场景
- 战斗过程中一笔带过的地点(如"打遍三界"、"从山上打到山下"、"从天宫打到凡间"等表示战斗范围的修辞)
📋【判断标准】:
问自己:这个场景是否需要单独制作一张背景图?角色是否在此场景有实际戏份?
如果只是一句话带过的地点,则不提取。
如果是表示"打斗范围"的修辞(如从天堂到地狱),则不提取。
🔄【去重规则】:
- 若场景在库中已存在则跳过,场景库如下:{locations_lib_name}
- 同一场景不同称呼合并为一个(如"书房"和"张先生的书房"视为同一场景)
- 返回的场景名必须与资产库中已有名称完全一致
【场景生成要求 - A: 全景空间版】
侧重点:宽广完整的空间全貌、整体布局、画面层次
⚠️ 【核心要求】必须生成【宽广的空间全景】,展示场景的完整面貌,而非局部特写!
- 镜头应该是【广角/远景】视角,能看到整个空间的全貌
- 展示空间的完整边界(墙壁、地面、天花板/天空)
- 让观众能够清晰理解这是一个什么样的完整空间
- 严格按照原文的场景描述来描写,原文描述的场景是最优先级,其他才可以自由发挥
1. **开头必须明确写明场景名称**:
- 每条描述开头必须以「场景名」的形式标注空间属性
- 示例:「皇宫」殿内铺设着... / 「客厅」窗外阳光透过... / 「卧室」床边放着...
- 这样AI在生成图片时能明确理解这是什么类型的空间
2. 每个场景生成 3 条中文环境描述(用于AI图片生成),供用户选择
3. 3条描述要求:
- 全部符合原文描述的场景特征
- 可以自由发挥细节,但整体风格保持一致,不要有过大差异
- 全部使用广角/远景视角,展示完整空间全貌
- 每条描述开头都必须以「场景名」标注
4. 每条描述都必须包含:
**宽广空间感**(最重要):
- 必须是【广角镜头】或【远景视角】,能看到空间的大部分区域
- 室内场景:能看到2-3面墙壁、地板、部分天花板
- 室外场景:能看到开阔的视野、远处的地平线或建筑群
- 强调空间的【开阔感】和【完整性】
**空间定位与规模**:
- 场景类型(室内/室外/幻想空间)
- 空间大小感:描述实际的空间尺度(如"约30平米的客厅"/"一眼望不到边的草原")
- 层高/纵深感:能看到的最远距离
**空间层次**(创造画面深度):
- 前景:靠近镜头的元素(桌角/门框边缘/植物叶片/栏杆等,部分可见)
- 中景:主要场景区域(核心物体的完整呈现)
- 背景:远处可见的元素(窗外景色/远处墙面/天际线/门廊深处)
**物体布局**:
- 使用明确的位置词:左侧/右侧/中央/角落/靠窗/远处
- 描述物体之间的空间关系和前后层次
- 5-8件物体,每件都有位置说明
**光线方向**:光从哪个方向照入,照亮哪些区域
5. 描述规范:
- 强调位置关系词:前方、远处、左侧、角落、靠近、深处
- 长度 100-150 字
⚠️【场景图不能出现任何角色 - 核心规则】:
场景图的用途:场景图是纯粹的"背景板",主角和重要角色会在后期通过 AI 合成到背景上。
因此,场景描述中**绝对不能出现任何有名有姓的角色**。
❌ 错误示例(包含了角色):
- "两只猴王持棒对峙" → 错!猴王是角色,不能出现
- "张三站在门口迎接" → 错!张三是角色,不能出现
- "孙悟空和六耳猕猴在街上打斗" → 错!主角不能出现
✅ 正确示例(纯背景):
- "「古道」广角镜头展现蜿蜒在险峻石林间的黄土古道,前景几株枯松,中景道路宽阔平坦,尘土飞扬,背景是连绵群山。"
- "「宴会厅」大厅远处三两宾客交谈" → 可以!这是无名背景群众
- "「集市」街道上行人往来" → 可以!这是模糊的路人群众
📋 什么情况可以写人群?
- 只有无名的、模糊的背景群众可以出现(如"宾客"、"路人"、"行人"、"围观群众")
- 这些群众不能有具体描述,只能用模糊词汇
- 如果场景是私密空间或无人场景,保持空镜即可
- 不包含艺术风格描述,风格由系统自动添加
6. 场景命名规则:中文 "地点_时间/状态"
- 示例:"客厅_白天"/"空间站_夜间"/"仙宫_黄昏"/"森林_迷雾中"
7. 剧情中出现的关键元素必须在场景中体现(如椅子、桌子等)
8.如无特殊要求,使用用户输入的语言来进行场景生成,例如输入英文输出偏西方场景,中文则输出偏中国场景,但是原则要按照文字剧本里实际发生的地点为准,
【输出规范(只允许以下 JSON 结构;字段名中文;不得输出任何多余文字)】
{
"locations": [
{
"name": "场景_时间",
"summary": "场景简要说明(用途/人物关联,如:张三居住的主卧室、公司高层会议室等)",
"has_crowd": true/false,
"crowd_description": "人群类型描述(仅当has_crowd为true时填写,如:宴会宾客、集市人群、学生们等)",
"descriptions": [
"「场景名」场景环境描述1(如has_crowd为true则包含人群元素)",
"「场景名」场景环境描述2",
"「场景名」场景环境描述3"
]
}
]
}
【严格性】
- 若无符合条件的场景,locations数组返回 []。
- 只返回上述 JSON;不得输出markdown代码块标记、如```json注释或解释;不得添加未定义字段。
- 每条描述必须遵守长度限制(100-150字);发现超长请自行截断。
- 禁止在 JSON 字符串值中出现英文双引号 "。原文中的所有引号(""''等)必须统一替换为「」。如字符串内确实需要英文双引号,必须转义为 \"
【原文内容如下】
{input}
================================================
FILE: lib/prompts/novel-promotion/single_panel_image.en.txt
================================================
You are a professional storyboard image artist.
Generate exactly one high-quality image for one panel.
Absolute constraints:
1. No text in the image.
2. No subtitles, labels, numbers, watermarks, or symbols.
3. Do not create collage or multi-frame output.
4. Output exactly one frame.
Aspect ratio (must be exact):
{aspect_ratio}
Storyboard panel data:
{storyboard_text_json_input}
Source text:
{source_text}
Style requirement:
{style}
Execution rules:
1. Respect panel composition, character placement, and action logic.
2. Use reference images for style/identity consistency only.
3. Repaint the background according to shot type and angle.
4. If storyboard conflicts with source text, keep narrative logic from source text.
5. Keep final visual style consistent with provided references.
================================================
FILE: lib/prompts/novel-promotion/single_panel_image.zh.txt
================================================
你是一位专业的分镜画师。请根据以下分镜数据生成单张高质量的镜头图片。
【绝对禁止 - 图像中不得出现任何文字 - 最高优先级】
生成的图像中绝对禁止出现任何文字:
- 禁止出现镜头类型标签(特写、中景、全景等)
- 禁止出现镜头运动文字(推、拉、摇等)
- 禁止出现数字或画面编号(1、2、3等)
- 禁止出现中文或英文文字
- 禁止出现水印、注释或符号
- 参考图上的文字标签仅供你识别使用,禁止画入图中
- 所有输入信息都是给你的指令,不是要画进图像的内容
纯视觉内容!纯视觉内容!纯视觉内容!
- 每个图片只能有一张镜头,禁止一张图片多张镜头,禁止拼图,禁止生成多张图,禁止生成一张图片里面三张图
【⚠️ 画面比例 - 必须严格遵守】
本次生成的画面比例为:{aspect_ratio}
- 必须严格按照此比例生成画面
- 禁止输出与指定比例不符的图像
- 禁止因参考图比例影响输出比例
- 每张输出的图片里面只能有一张图,禁止一张图片多张图!
【参考图使用规则】
- 角色参考:用于参考角色外貌、服装、面部特征、体型
- 场景参考:仅用于参考环境的构图布局风格和氛围,需根据画面重新绘制,不要直接贴在背景上使用
- 画面的背景必须根据镜头角度和景别重新绘制
- 特写/细节镜头应使用虚化或局部背景
- 参考图上方的文字标签标注了角色/场景名称,请与分镜要求对应
【⚠️ 摄影规则 - 关键】
如果分镜数据中包含 photography_rules,必须严格遵守:
- **光照方向**:按照 lighting.direction 的描述绘制光源方向
- **角色位置**:按照 characters 数组中的 screen_position 确定角色在画面中的位置(左/右/中央)
- **角色姿势**:按照 characters 数组中的 posture 确定角色姿态(站立/坐着等)
- **景深**:按照 depth_of_field 的描述控制前后景虚实
- **色调**:按照 color_tone 的描述确定整体色彩氛围
【分镜内容要求】
- 根据分镜数据设计画面的视觉内容
- 确保镜头方向一致(不跳轴),角色位置正确
- 严格按照文字分镜要求绘制镜头
【⚠️ 原文优先原则 - 重要】
分镜描述可能存在空间/位置错误。务必与原文交叉验证:
- 当分镜与原文冲突时:按原文的空间关系、角色位置、动作顺序
- 参考分镜的:镜头类型、构图、摄影角度
- 参考原文的:剧情逻辑、空间关系、角色互动、动作顺序
- 智能结合两者生成最准确的视觉效果
【绝对规则 - 严格遵守】
- 严格按照分镜要求绘制画面
- 禁止添加、删除或重排任何镜头
- 镜头必须与输入完全匹配
【分镜数据】
{storyboard_text_json_input}
【镜头原文】
{source_text}
【⚠️ 风格要求 - 必须严格遵守】
画面风格:{style}
- 必须严格遵循上传的角色和场景参考图的美术风格
- 角色绘制风格、线条、色彩必须与角色参考图匹配
- 环境风格、氛围、色调必须与场景参考图匹配
- 禁止出现与参考图风格不一致的情况!
================================================
FILE: lib/prompts/novel-promotion/storyboard_edit.en.txt
================================================
You are an expert storyboard image editor.
Edit a single panel image or a panel set according to user instruction.
Rules:
1. Do not add any text overlay, subtitle, or technical labels.
2. If user uploaded reference images, use them as primary visual guidance.
3. Keep identity and scene continuity unless user requests a change.
User instruction:
{user_input}
Return only the edited image result.
================================================
FILE: lib/prompts/novel-promotion/storyboard_edit.zh.txt
================================================
你是一个修改编辑图片的大师,你需要根据用户的指令来编辑单个镜头或整组分镜图片,编辑时需要遵守以下规则:
1:不要添加任何多余标识符文字,如字幕,景别信息等
2:如果用户上传的有图片,那么就按照图片来进行参考修改
用户的编辑指令如下:{user_input}
请根据指令和原图,输出修改后的图片
================================================
FILE: lib/prompts/novel-promotion/voice_analysis.en.txt
================================================
You are a dialogue voice-line analyzer.
Extract spoken lines from text, assign speaker, estimate emotion intensity, and map to storyboard panels.
Output format (JSON array only):
[
{
"lineIndex": 1,
"speaker": "Speaker name",
"content": "Dialogue line",
"emotionStrength": 0.3,
"matchedPanel": {
"storyboardId": "storyboard_id",
"panelIndex": 0
}
}
]
Input text:
{input}
Character library:
{characters_lib_name}
Character introductions:
{characters_introduction}
Storyboard JSON:
{storyboard_json}
Rules:
1. Extract spoken dialogue only (quoted speech, direct speech, inner speech that should be voiced).
2. Exclude pure narration, action-only description, and scene-only description.
3. emotionStrength must be between 0.1 and 0.5.
4. Match panel by order + speaker consistency + semantic relevance.
5. If no reliable panel match exists, set "matchedPanel": null.
6. Use canonical names from character library when possible.
7. If there is no spoken dialogue that should be voiced, return [].
8. Return strict JSON only, no markdown.
9. ⚠️ JSON SAFETY: All quotation marks in dialogue (""''「」 etc.) MUST be converted to corner brackets「」in JSON string values. NEVER use raw ASCII double quotes " inside string values.
================================================
FILE: lib/prompts/novel-promotion/voice_analysis.zh.txt
================================================
你是"台词发言人分析大师"。
任务:从文本中提取需要配音的**对话台词**,分析情绪强度,并匹配对应的视频镜头。
输出格式(只返回JSON,禁止markdown标记):
[
{
"lineIndex": 1,
"speaker": "发言人名称",
"content": "台词内容",
"emotionStrength": 0.5,
"matchedPanel": {
"storyboardId": "分镜组ID",
"panelIndex": 0
}
}
]
分析规则:
1. 【台词提取 - 最重要】
✅ 只提取以下类型的内容:
- **带引号的对话**:"xxx" 或 "xxx" 或 「xxx」
- **直接引语**:他说:"xxx"、她喊道:"xxx"
- **内心独白**:我心想:"xxx"
❌ 严格排除以下内容:
- 叙述性文字(无引号的描述)
- 动作描写(描述角色的动作)
- 场景描述(描述环境、画面)
- 章节标题
- 明确设定为无语言、默片、纯画面表达的内容
⚠️ 判断标准:这句话是否需要有人"说出来"?如果只是描述画面动作,不要提取。
⚠️ 如果全文没有任何需要配音的台词,直接返回 []。
2. 【情绪强度 emotionStrength】
根据台词的情绪激烈程度,输出0.1-0.5之间的数值(⚠️ 注意:最高不超过0.5,保持语音自然平稳):
| 情绪类型 | 强度范围 | 示例 |
|---------|---------|------|
| 平静/陈述 | 0.1-0.15 | "好的,我知道了" |
| 普通对话 | 0.15-0.2 | "你今天怎么来了?" |
| 疑惑/好奇 | 0.2-0.25 | "这是怎么回事?" |
| 惊讶/意外 | 0.25-0.3 | "什么?!你说真的?" |
| 生气/愤怒 | 0.3-0.35 | "你给我滚出去!" |
| 悲伤/哭泣 | 0.25-0.35 | "为什么要这样对我..." |
| 狂喜/激动 | 0.35-0.4 | "太好了!我们成功了!" |
| 咆哮/嘶吼 | 0.4-0.5 | "我要杀了你!!!" |
3. 【发言人识别】
- 对话内容:识别说话者,如"他说"、"她喊道"
- 无引导词的引号内容:根据上下文推断发言人
- 如果无法确定发言人,设为"旁白"
4. 【角色匹配】
- 角色库:{characters_lib_name}
- 角色介绍:{characters_introduction}
- 优先使用角色库中完全一致的名称
- ⭐ 参考角色介绍理解"我"和其他称呼对应的角色
- 如果不存在,使用原文中的称呼
5. 【镜头匹配 - 严格规则】
⚠️ 这是关键步骤,必须严格遵守以下规则:
a) **顺序约束**:
- 台词在原文中的出现顺序必须与分镜顺序大致对应
- 第N条台词应该匹配在第N个分镜附近,不能跳跃太远
- 禁止乱序匹配(如第5条台词匹配到第1个分镜)
b) **发言人校验**:
- 台词的speaker必须与分镜的characters字段中的角色对应
- 如果分镜画面角色是"玄离",不能匹配"柳如烟"的台词
- 对话场景:谁说话,就匹配包含说话者的分镜
c) **内容匹配**:
- 优先匹配台词内容完全包含在分镜text_segment中的情况
- 其次匹配台词内容与text_segment语义相近的情况
d) **匹配策略**:
1. 首先根据text_segment精确匹配台词内容
2. 验证分镜角色是否包含台词发言人
3. 验证顺序是否合理(前后3个分镜范围内)
4. 如果无法满足以上条件,matchedPanel设为null
e) **示例**:
- 原文顺序:柳如烟说"殿下身份尊贵" → 玄离说"胆子挺大"
- 分镜顺序:分镜15(柳如烟特写) → 分镜16(玄离特写)
- 正确匹配:柳如烟台词→分镜15,玄离台词→分镜16
- 错误匹配:柳如烟台词→分镜16(发言人不匹配)
6. 【多音字处理 - 重要】
为确保TTS语音合成发音正确,对于容易被误读的多音字,需要替换为**读音完全相同(包括声调)的单音字**。
处理原则:
a) **识别多音字**:找出台词中的多音字(如:还、行、了、乐、朝、重、都等)
b) **判断正确读音**:根据上下文语义判断该字在此处的正确读音
c) **选择替换字**:找一个读音完全相同(声母、韵母、声调都一致)的常用单音字替换
d) **验证替换**:确保替换后的字读音与原意读音完全一致
替换示例思路:
- "还(huán)给我" → 用"环"替换,因为"环"只读huán
- "银行(háng)" → 用"航"替换,因为"航"只读háng
- "了(liǎo)解" → 用"聊"替换,因为"聊"只读liáo(接近liǎo)
- "快乐(lè)" → 用"乐"保持原字,因为TTS通常能正确读常见词
- "重(zhòng)量" → 用"众"替换,因为"众"只读zhòng
⚠️ 注意事项:
- 必须确保替换字的读音与目标读音完全一致,不要用读音相近但不同的字
- 例如:"勒"读lè或lēi,不能用来替换读le的字
- 常见词组(如"了解"、"快乐"、"音乐")TTS通常能正确读,可以保持原字
- 只有当多音字在特定语境下容易被TTS误读时才需要替换
7. 【JSON安全输出】
⚠️ 原文中的所有引号(""''「」等)在 JSON 字符串值中必须统一替换为「」,严禁出现未转义的英文双引号 "
- ✅ 正确:"content":"「你好啊」"
- ❌ 错误:"content":""你好啊""
分镜数据如下:
{storyboard_json}
原文如下:
{input}
================================================
FILE: lib/prompts/proxy.ts
================================================
export async function setProxy() {
if (process.env.PROXY_URL) { // If you are in China, you must use this proxy:
const { setGlobalDispatcher, ProxyAgent } = await import("undici");
const proxyAgent = new ProxyAgent(process.env.PROXY_URL);
setGlobalDispatcher(proxyAgent);
}
}
================================================
FILE: lib/prompts/skills/api-config-template.system.txt
================================================
你是 API 配置助手,负责把第三方 API 文档映射为可执行模型模板并保存。
目标:
1) 最少追问,只收集运行必填字段。
2) 字段齐全后立刻调用工具保存。
3) 绝不输出臆测字段;不确定就追问。
工具调用规则:
1) 单模型保存:saveModelTemplate
2) 多模型一次配置:saveModelTemplates(优先批量)
3) compatMediaTemplate 必须是 JSON 对象,不能是 JSON 字符串
4) 保存前先自检字段名,严禁使用 submit/query 这类旧字段
模板结构必须是:
{
"version": 1,
"mediaType": "image" | "video",
"mode": "sync" | "async",
"create": { "method": "...", "path": "...", "contentType"?: "...", "bodyTemplate"?: {}, "multipartFileFields"?: ["..."] },
"status"?: { "method": "...", "path": "..." },
"content"?: { "method": "...", "path": "..." },
"response": {
"taskIdPath"?: "$....",
"statusPath"?: "$....",
"outputUrlPath"?: "$....",
"outputUrlsPath"?: "$....",
"errorPath"?: "$...."
},
"polling"?: {
"intervalMs": 5000,
"timeoutMs": 600000,
"doneStates": ["completed"],
"failStates": ["failed", "error"]
}
}
严格校验约束:
1) version 只能是 1
2) mediaType 只能 image/video
3) mode 只能 sync/async
4) create.path 必填
5) create.method 为 POST/PUT/PATCH 时 create.bodyTemplate 必填
6) mode=async 时 status.path 必填,且必须包含 {{task_id}}
7) mode=async 时 response.taskIdPath、response.statusPath、polling 必填
8) mode=sync 时 response.outputUrlPath 或 response.outputUrlsPath 至少一个
9) JSONPath 字段必须以 $. 开头
10) 只有 contentType=multipart/form-data 时才能使用 multipartFileFields;且 multipartFileFields 中的字段必须存在于 bodyTemplate 中
最小必问项(只问这些):
1) modelId(每个模型)
2) name(每个模型;无则默认 modelId)
3) type(image/video)
4) create endpoint(method + path + body 字段映射)
5) async 场景下 status endpoint(method + path,且 path 必须带 {{task_id}})
6) async 场景下必须明确 taskIdPath、statusPath、polling.doneStates、polling.failStates
7) 若 create.contentType 是 multipart/form-data,必须明确哪些字段是文件字段,并写入 multipartFileFields
8) sync 场景下必须明确 outputUrlPath 或 outputUrlsPath
禁止隐式默认:
1) 不得擅自补 taskIdPath/statusPath/outputUrlPath
2) 不得擅自补 polling.doneStates/failStates
3) 文档未明确时必须追问,不能猜
4) 只有 create.contentType 未写明时,可根据请求示例判断为 application/json 或 multipart/form-data
video create bodyTemplate 默认建议(除非文档明确不同):
{
"model": "{{model}}",
"prompt": "{{prompt}}",
"seconds": "{{duration}}",
"size": "{{size}}",
"input_reference": "{{image}}"
}
path 选择规则(非常重要):
1) 若文档给了完整 URL,直接完整保留
2) 若文档给的是相对路径,原样保留,不要擅自去掉 /v1、/v2 或其他前缀
3) 不允许自作主张改写版本前缀
交互风格:
1) 不展示思维链路,不输出 <think>
2) 先给简短中文结论,再给补充问题或执行结果
3) 字段齐全时直接调用工具,不要重复确认
4) 工具返回 invalid 时,逐条复述 issues 并只追问缺失字段,然后再次调用工具
当前 providerId={{providerId}}
================================================
FILE: lib/prompts/skills/tutorial.system.txt
================================================
你是产品教程助手。
目标是给出准确、可执行、简洁的操作步骤。
当用户信息不足时先提问,不要猜测。
禁止编造不存在的页面、按钮、接口或参数。
================================================
FILE: messages/en/actions.json
================================================
{
"storyboard": "Storyboard",
"storyboard_candidate": "Storyboard Candidate",
"character": "Character",
"location": "Location",
"video": "Video",
"analyze": "Analyze",
"analyze_character": "Character Analysis",
"analyze_location": "Location Analysis",
"clips": "Clip Splitting",
"storyboard_text_plan": "Storyboard Planning",
"storyboard_text_detail": "Storyboard Details",
"tts": "Text-to-Speech",
"regenerate": "Regenerate",
"voice-generate": "Voice Generation",
"voice-design": "Voice Design",
"lip-sync": "Lip Sync"
}
================================================
FILE: messages/en/apiConfig.json
================================================
{
"title": "API Configuration",
"saving": "Saving...",
"saved": "Saved",
"saveFailed": "Save failed",
"connected": "Connected",
"notConfigured": "Not configured",
"configure": "Configure",
"connect": "Connect",
"compatibilityLayerOpenAI": "OpenAI Compatible Layer",
"compatibilityLayerGemini": "Gemini Compatible Layer",
"show": "Show",
"hide": "Hide",
"capability": "Models",
"default": "Default",
"delete": "Delete",
"add": "Add",
"cancel": "Cancel",
"close": "Close",
"save": "Save",
"comingSoon": "Coming soon",
"priceInput": "Input {amount}",
"priceOutput": "Output {amount}",
"priceUnavailable": "N/A",
"fillComplete": "Please fill in all fields",
"fillPricing": "Please fill in pricing information",
"pricingInputLabel": "Input price",
"pricingOutputLabel": "Output price",
"pricingBasePriceLabel": "Base price (optional)",
"pricingEnableCustom": "Enable custom pricing (optional)",
"pricingOptionPricesPlaceholder": "Option pricing JSON (optional), e.g. {\"resolution\":{\"1024x1024\":0.12}}",
"modelIdExists": "Model ID already exists",
"flushConfigFailed": "Failed to save provider settings. Please save API key/Base URL first.",
"probeLlmProtocolFailed": "Model protocol probe failed. Please try again.",
"probeAuthFailed": "Model protocol probe authentication failed. Please check API key.",
"probeInconclusive": "Model protocol probe is inconclusive (rate limit or provider error). Please retry later.",
"probeRequestFailed": "Model protocol probe request failed. Please try again.",
"modelDisplayName": "Display Name (for your reference)",
"modelActualId": "Actual Model ID (API parameter)",
"noModelsForProvider": "No models configured for this provider",
"defaultModels": "Default Model Configuration",
"textDefault": "Text Model",
"characterDefault": "Character Model",
"locationDefault": "Location Model",
"storyboardDefault": "Storyboard Model",
"editDefault": "Edit Model",
"videoDefault": "Video Model",
"audioDefault": "Voice Model",
"lipsyncDefault": "Lip Sync Model",
"selectDefault": "Select",
"defaultModelDesc": {
"analysisModel": "Handles script analysis, storyboard construction and full-pipeline text reasoning.",
"videoModel": "Synthesizes images and instructions into final video clips.",
"characterModel": "Generate character portraits and appearance references from script descriptions",
"locationModel": "Generate scene environments and spatial references from script descriptions",
"storyboardModel": "Generate shot frames and visual references from storyboard scripts",
"editModel": "Perform localized edits, style adjustments and refinements on existing images",
"audioModel": "Convert text dialogue into natural and fluent speech audio",
"lipSyncModel": "Precisely synchronize speech audio with video character lip movements",
"voiceDesignModel": "Design custom voice tones and speech style profiles for characters"
},
"defaultModelSection": {
"coreFoundation": "Text Analysis & Video",
"creativePipeline": "Global Image Model Config",
"unifiedOverride": "Batch Image Model Config",
"unifiedOverrideHint": "Set the model responsible for image generation/editing across the entire system",
"unifiedOverridePlaceholder": "Apply to all scenes...",
"followUnified": "Follow global config",
"extensions": "Extensions",
"coreTextTitle": "Text Analysis Model",
"coreVideoTitle": "Video Generation Model",
"pipelineCharacter": "Character Gen",
"pipelineLocation": "Scene Gen",
"pipelineStoryboard": "Shot Gen",
"pipelineEdit": "Image Edit",
"extLipSync": "Lip Sync",
"extTTS": "Speech Synthesis",
"extVoiceDesign": "Voice Design",
"corePlaceholder": "Required",
"extPlaceholder": "Not enabled"
},
"imageModelTip": "We recommend using Google's Banana series models. Other image models currently have limited generation quality.",
"customProviderTip": "This project is currently in beta. Due to varying custom API formats across providers, custom API compatibility is still limited. We recommend using the built-in official APIs. Future updates will expand compatibility with more providers.",
"providerPool": "Provider Pool",
"providerPoolDesc": "Configure and use a rich selection of models from global providers",
"dragToSort": "Drag to sort",
"dragToSortHint": "Drag the top-left handle on each card to reorder providers",
"hideProvider": "Hide provider",
"hideProviderConfirm": "Are you sure you want to hide this provider? It will be moved to the bottom and can be restored at any time.",
"showProvider": "Show provider",
"showHiddenProviders": "Show hidden providers",
"hideHiddenProviders": "Hide hidden providers",
"hiddenProvidersPrefix": "Hidden",
"providerIdExists": "Provider ID already exists",
"presetProviderCannotDelete": "Preset providers cannot be deleted",
"confirmDeleteProvider": "Are you sure you want to delete this provider?",
"presetModelCannotDelete": "Preset models cannot be deleted",
"confirmDeleteModel": "Are you sure you want to delete this model?",
"addGeminiProvider": "Add Model Provider",
"baseUrl": "Base URL",
"configureBaseUrl": "Configure URL",
"addModel": "Add Model",
"batchModeHalfPrice": "Batch mode (50% price)",
"openaiCompatVideoOnlyHint": "Only OpenAI official-format image-to-video models are supported.",
"typeText": "Text",
"typeImage": "Image",
"typeVideo": "Video",
"typeAudio": "Audio",
"apiKeyLabel": "API Key",
"apiType": "API Type",
"apiTypeGeminiCompatible": "Gemini Compatible",
"apiTypeOpenAICompatible": "OpenAI Compatible",
"apiTypeGeminiHint": "Uses Google SDK",
"otherProviders": "Other Settings",
"audioCategory": "Audio",
"audioAndLipsync": "Audio & Lip Sync",
"configureApiKey": "Configure API Key",
"enterApiKey": "Enter API Key",
"testConnection": "Test Connection",
"testing": "Testing...",
"testPassed": "Connection test passed",
"testFailed": "Connection test failed",
"testWarning": "We recommend checking your configuration before adding",
"testRetry": "Retry",
"addAnyway": "Add Anyway",
"testStep": {
"models": "Model List",
"textGen": "Text Generation",
"imageGen": "Image Generation",
"credits": "Credits Check",
"audioGen": "Audio Generation",
"skipped": "Skipped"
},
"tabs": {
"llm": "Text Models",
"image": "Image Models",
"video": "Video Models",
"audio": "Audio Models",
"other": "Other"
},
"sections": {
"llmApiKeys": "Text Model API Keys",
"imageApiKeys": "Image Model API Keys",
"videoApiKeys": "Video Model API Keys",
"audioApiKey": "Audio Model API Key",
"lipsyncApiKey": "Lip Sync API Key"
},
"defaultModel": {
"title": "Default Model",
"hint": "New projects and Asset Hub will use this default configuration. You can also customize models per project in project settings.",
"notSelected": "Not selected",
"analysis": "Analysis Model",
"image": "Image Generation",
"video": "Video Generation",
"resolution": "Image Resolution"
},
"workflowConcurrency": {
"analysis": "Analysis Concurrency",
"image": "Image Concurrency",
"video": "Video Concurrency"
},
"viewTutorial": "View Tutorial",
"tutorial": {
"button": "Tutorial",
"title": "Setup Guide",
"subtitle": "Follow these steps to complete the configuration",
"close": "Got it",
"openLink": "Open link",
"steps": {
"ark_step1": "Go to the Volcano Engine console to create an API Key",
"ark_step2": "On the model management page, click 'Enable All Models' button in the top right corner",
"openrouter_step1": "Go to OpenRouter platform and create an API Key (must select models with image capabilities)",
"fal_step1": "Go to FAL platform and create an API Key",
"google_step1": "Go to Google AI Studio and create an API Key",
"minimax_step1": "Go to MiniMax platform and get an API Key",
"vidu_step1": "Go to the Vidu platform and click 'Create API Key'",
"openai_compatible_step1": "Enter any OpenAI-compatible service Base URL and API key",
"gemini_compatible_step1": "Enter any Gemini-compatible service Base URL and API key",
"bailian_step1": "Go to Alibaba Cloud Bailian console and get an API Key",
"siliconflow_step1": "Go to SiliconFlow console and create an API Key"
}
},
"assistantOpen": "AI Assistant",
"assistantTitle": "AI Config Assistant",
"assistantSubtitle": "Convert third-party docs into executable image/video templates and auto-save the model.",
"assistantWelcome": "Describe your API docs (endpoint, request body, response fields). I will ask follow-up questions and auto-save once valid.",
"assistantInputPlaceholder": "Paste docs or describe endpoint details...",
"assistantSend": "Send",
"assistantDisabledHint": "Save API key and Base URL first",
"assistantRequestFailed": "Assistant request failed. Please try again later.",
"assistantResponseInvalid": "Assistant response format is invalid. Please retry.",
"assistantMissingTitle": "Missing fields",
"assistantWarningsTitle": "Warnings",
"assistantDraftTitle": "Current draft model",
"assistantReasoningTitle": "Reasoning",
"assistantReasoningExpand": "Show",
"assistantReasoningCollapse": "Hide",
"assistantCompletedTitle": "Template Saved",
"assistantCompletedMessage": "Model {model} has been added to this provider.\nClick close to finish this chat.",
"you": "You",
"thinking": "Thinking..."
}
================================================
FILE: messages/en/apiTypes.json
================================================
{
"image": "Image Generation",
"video": "Video Generation",
"text": "Text Analysis",
"tts": "Text-to-Speech",
"voice": "Voice Dubbing",
"voice_design": "Voice Design",
"lip_sync": "Lip Sync"
}
================================================
FILE: messages/en/assetHub.json
================================================
{
"title": "Asset Hub",
"description": "Manage your global character and location assets",
"modelHint": "Asset Hub uses default models. To change settings, go to",
"modelHintLink": "API Settings",
"modelHintSuffix": "",
"folders": "Folders",
"noFolders": "No folders yet",
"allAssets": "All Assets",
"characters": "Characters",
"locations": "Locations",
"voices": "Voices",
"addCharacter": "Add Character",
"addLocation": "Add Location",
"addVoice": "Add Voice",
"downloadAll": "Download All",
"downloadAllTitle": "Download All Image Assets as ZIP",
"downloading": "Packing...",
"downloadSuccess": "Download Complete",
"downloadFailed": "Download Failed",
"downloadEmpty": "No image assets to download",
"newFolder": "New Folder",
"editFolder": "Edit Folder",
"deleteFolder": "Delete Folder",
"folderName": "Folder Name",
"folderNamePlaceholder": "Enter folder name",
"emptyState": "No assets yet",
"emptyStateHint": "Click the buttons above to add characters or locations",
"generate": "Generate",
"generating": "Generating...",
"regenerate": "Regenerate",
"undo": "Undo",
"delete": "Delete",
"cancel": "Cancel",
"save": "Save",
"create": "Create",
"confirmDeleteFolder": "Delete this folder? Assets inside will be moved to uncategorized.",
"confirmDeleteCharacter": "Delete this character? This action cannot be undone.",
"confirmDeleteLocation": "Delete this location? This action cannot be undone.",
"confirmDeleteVoice": "Delete this voice? This action cannot be undone.",
"voiceName": "Voice Name",
"voiceNamePlaceholder": "Enter voice name",
"voiceNameRequired": "Please enter a voice name",
"voicePickerTitle": "Select from Voice Library",
"voicePickerEmpty": "No voices yet. Please create a voice first.",
"voicePickerConfirm": "Confirm Selection",
"pagination": {
"previous": "Previous",
"next": "Next"
},
"common": {
"cancel": "Cancel"
},
"generateFailed": "Generation failed",
"selectFailed": "Selection failed",
"uploadFailed": "Upload failed",
"editFailed": "Edit failed",
"saveVoiceFailed": "Failed to save voice",
"saveVoiceFailedDetail": "Failed to save voice: {error}",
"bindVoiceFailed": "Failed to bind voice",
"bindVoiceFailedDetail": "Failed to bind voice: {error}",
"voiceDesignSaved": "AI-designed voice has been set for {name}",
"appearanceLabel": "Appearance {index}",
"voiceSettings": {
"title": "Voice",
"noVoice": "No voice",
"previewFailed": "Preview failed: {error}",
"uploadFailed": "Upload audio failed: {error}",
"uploading": "Uploading...",
"uploaded": "Uploaded",
"uploadAudio": "Upload Audio",
"aiDesign": "AI Design",
"voiceLibrary": "Voice Library",
"pause": "Pause",
"preview": "Preview Voice"
},
"modal": {
"newCharacter": "New Character",
"confirm": "Confirm",
"processing": "Processing...",
"newLocation": "New Location",
"addCharacter": "Create Character",
"addLocation": "Create Location",
"adding": "Creating...",
"aiDesign": "AI Design",
"aiDesignPlaceholder": "e.g., A beautiful woman in a red traditional dress with flowing long hair",
"aiDesignLocationPlaceholder": "e.g., A classical Chinese garden with rockery and pavilions",
"aiDesignTip": "AI will generate a detailed description based on your input. You can edit it after generation.",
"aiDesignLocationTip": "AI will generate a detailed scene description based on your input",
"generate": "Generate",
"generating": "Generating...",
"nameLabel": "Character Name",
"namePlaceholder": "Enter character name",
"descLabel": "Character Description",
"descPlaceholder": "Describe the character's appearance, clothing, hairstyle, etc...",
"locationNameLabel": "Location Name",
"locationNamePlaceholder": "Enter location name",
"locationSummaryLabel": "Location Description",
"locationSummaryPlaceholder": "Describe the environment, atmosphere, features, etc...",
"referenceUpload": "Upload Reference",
"referenceUploadTip": "Upload a character image, AI will convert it to a three-view design sheet",
"convertToCharacter": "Convert to 3-View",
"converting": "Converting...",
"dropOrClick": "Drop image or click to upload",
"supportedFormats": "JPG, PNG supported"
}
}
================================================
FILE: messages/en/assetLibrary.json
================================================
{
"title": "Asset Library",
"button": "Assets",
"characters": "Characters",
"locations": "Locations",
"noCharacters": "No characters",
"noLocations": "No locations",
"addCharacter": "Add Character",
"addLocation": "Add Location",
"generateImage": "Generate Image",
"regenerateImage": "Regenerate",
"analyzeAssets": "Analyze Assets",
"analyzing": "Analyzing..."
}
================================================
FILE: messages/en/assetModal.json
================================================
{
"character": {
"title": "New Character",
"name": "Character Name",
"namePlaceholder": "Enter character name",
"modeReference": "Reference Image",
"modeDescription": "Description",
"isSubAppearance": "This is a sub-appearance",
"isSubAppearanceHint": "Add a new appearance state for an existing character",
"uploadReference": "Upload Reference",
"pasteHint": "Ctrl+V to paste",
"dropOrClick": "Click to upload or drag image",
"supportedFormats": "Supports JPG, PNG formats",
"nameRequired": "Please enter character name first to use reference conversion",
"convertToSheet": "Convert to standard character sheet",
"useReferenceGeneratePrefix": "Generate from reference",
"generateCountSuffix": "images",
"selectReferenceGenerateCount": "Select reference generation count",
"referenceTip": "Upload any character image, AI will generate a standard character sheet",
"description": "Character Description",
"modifyDescription": "Modify Description",
"descPlaceholder": "Enter character appearance description...",
"modifyDescriptionPlaceholder": "Describe how to modify the primary appearance, e.g. formal outfit, post-battle injuries, add a cloak...",
"selectMainCharacter": "Select Main Character",
"selectCharacterPlaceholder": "Please select a character...",
"appearancesCount": "{count} appearances",
"changeReason": "Appearance Change Reason",
"changeReasonPlaceholder": "e.g. injured after battle, changed into formal wear for a banquet...",
"defaultDescription": "{name}'s character profile",
"generationMode": "Generation Mode",
"directGenerate": "Direct Generate",
"extractPrompt": "Extract Prompt",
"extractFirst": "Extract Description First",
"directGenerateDesc": "Directly generate character sheet from reference (img2img)",
"extractPromptDesc": "Extract description from image first, then generate (txt2img)",
"maxReferenceImages": "Up to 5 reference images",
"selectedCount": "Selected {count}/5 images",
"extractDescription": "Extract Description",
"extracting": "Extracting...",
"extractedDescription": "Extracted Description (Editable)",
"reExtract": "Re-extract",
"editHint": "Edit the description, then click below to generate",
"generateFromDescription": "Generate from Description",
"textToImageTip": "Text-to-image mode: Generate from extracted description",
"pleaseExtractFirst": "Please extract character description first"
},
"location": {
"title": "New Location",
"name": "Location Name",
"namePlaceholder": "Enter location name",
"description": "Location Description",
"descPlaceholder": "Enter location description..."
},
"artStyle": {
"title": "Art Style"
},
"aiDesign": {
"title": "AI Design",
"placeholder": "Describe the character you want...",
"placeholderLocation": "Describe the scene atmosphere...",
"generating": "Designing...",
"generate": "Generate",
"tip": "Enter a simple description, AI will generate detailed settings"
},
"common": {
"creating": "Creating...",
"create": "Create",
"cancel": "Cancel",
"adding": "Adding...",
"add": "Add",
"addOnly": "Add character only",
"addOnlyToAssetHub": "Add only to asset hub",
"addOnlyLocation": "Add location only",
"addOnlyToAssetHubLocation": "Add location to asset hub only",
"addAndGeneratePrefix": "Add and generate",
"generateCountSuffix": "images",
"selectGenerateCount": "Select generation count",
"optional": "(Optional)"
},
"errors": {
"uploadFailed": "Upload failed",
"extractDescriptionFailed": "Failed to extract description",
"createFailed": "Creation failed",
"aiDesignFailed": "AI design failed",
"addSubAppearanceFailed": "Failed to add sub-appearance",
"insufficientBalance": "Insufficient balance"
}
}
================================================
FILE: messages/en/assetPicker.json
================================================
{
"selectCharacter": "Select Character from Asset Hub",
"selectLocation": "Select Location from Asset Hub",
"selectVoice": "Select Voice from Asset Hub",
"searchPlaceholder": "Search by name or folder...",
"noAssets": "No assets in Asset Hub",
"createInAssetHub": "Please create characters/locations/voices in Asset Hub first",
"noSearchResults": "No matching assets found",
"appearances": "appearances",
"images": "images",
"cancel": "Cancel",
"confirmCopy": "Confirm Copy",
"copyFromGlobal": "Copy from Asset Hub",
"copySuccess": "Copy successful",
"copyFailed": "Copy failed",
"preview": "Preview",
"stop": "Stop"
}
================================================
FILE: messages/en/assets.json
================================================
{
"stage": {
"title": "Assets Confirmation",
"characters": "Characters",
"locations": "Locations",
"analyze": "Analyze Assets",
"analyzing": "Analyzing...",
"generateAll": "Generate All",
"noCharacters": "No characters",
"noLocations": "No locations",
"confirmProfiles": "Character Profiles to Confirm",
"confirmHint": "Please confirm these profiles before generating descriptions",
"confirmAll": "Confirm All ({count})",
"assetsTitle": "Asset Analysis",
"characterAssets": "Character Assets",
"locationAssets": "Location Assets",
"counts": "{characterCount} Characters, {appearanceCount} Appearances",
"locationCounts": "{count} Locations",
"undoFailed": "Undo failed",
"undoFailedError": "Undo failed: {error}",
"undoSuccess": "Reverted to previous version",
"editFailed": "Edit failed",
"editFailedError": "Image edit failed: {error}",
"updateSuccess": "Description updated successfully"
},
"character": {
"add": "Add Character",
"edit": "Edit Character",
"delete": "Delete Character",
"deleteConfirm": "Delete this character?",
"deleteAppearanceConfirm": "Delete this appearance?",
"deleteFailed": "Delete failed: {error}",
"deleteWhole": "Delete Whole Character",
"deleteOptions": "Delete Options",
"name": "Character Name",
"description": "Appearance Description",
"generateImage": "Generate Profile",
"regenerateImage": "Regenerate",
"generate": "Generate",
"regenerating": "Generating...",
"profile": "Profile",
"voiceSettings": "Voice Settings",
"speaker": "Speaker",
"selectSpeaker": "Select Speaker",
"noSpeaker": "Not Set",
"primary": "Primary",
"secondary": "Secondary",
"generateFromPrimary": "Generate from Primary",
"selectPrimaryFirst": "Select primary first",
"editing": "Editing...",
"confirming": "Confirming...",
"assetCount": "{count} Appearances",
"characterCount": "{count} Characters",
"updateFailed": "Update description failed",
"addFailed": "Add character failed",
"copyFromGlobal": "Copy from Asset Hub"
},
"location": {
"add": "Add Location",
"edit": "Edit Location",
"delete": "Delete Location",
"deleteConfirm": "Delete this location?",
"deleteFailed": "Delete failed: {error}",
"name": "Location Name",
"summary": "Summary",
"summaryPlaceholder": "Usage/associations, e.g.: John's master bedroom",
"description": "Location Description",
"generateImage": "Generate Image",
"regenerateImage": "Regenerate",
"updateFailed": "Update description failed",
"addFailed": "Add location failed"
},
"image": {
"upload": "Upload Image",
"uploadReplace": "Upload Replacement",
"uploadFailed": "Upload Failed",
"uploadFailedError": "Upload failed: {error}",
"uploadSuccess": "Upload Success!",
"edit": "Edit Image",
"editPrompt": "Edit Prompt",
"undo": "Undo to Previous Version",
"undoConfirm": "Are you sure you want to undo to the previous version? Current version will be deleted.",
"regenerateGroup": "Regenerate Group",
"regenerateStuck": "Click to regenerate (if stuck)",
"selectCount": "Select generation count",
"generateCountPrefix": "Generate",
"generateCountSuffix": "images",
"regenCountPrefix": "Regenerate",
"regenCountSuffix": "",
"regenCountAriaLabel": "Select regeneration count",
"generatedProgress": "Generated {generated}/{total}",
"generating": "Generating",
"regenerating": "Regenerating",
"generatingPlaceholder": "Waiting",
"selectTip": "Once selected and confirmed, you can edit and modify the image",
"selectFirst": "Please select an image first",
"useThis": "Use this option",
"optionAlt": "{name} - Option {number}",
"optionNumber": "Option {number}",
"optionSelected": "Selected Option {number}",
"confirmOption": "Confirm Option {number}",
"deleteOthersHint": "(delete others)",
"confirmSuccess": "Selection confirmed",
"confirmFailed": "Confirm selection failed: {error}",
"selectFailed": "Select image failed: {error}",
"cancelSelection": "Cancel Selection",
"deleteThis": "Delete this appearance",
"undoFailed": "Undo failed",
"undoSuccess": "✓ Reverted to previous version",
"editFailed": "Image edit failed",
"editSuccess": "Image edit successful",
"regenerateFailed": "Regenerate failed: {error}"
},
"modal": {
"newCharacter": "New Character",
"addSubAppearance": "Add Sub-Appearance",
"aiDesign": "AI Design",
"aiDesigning": "Designing...",
"designInstruction": "Please enter design instruction",
"enterNameDesc": "Please enter character name and description",
"selectCharacter": "Please select a character",
"enterChangeReason": "Please enter change reason",
"enterSubDesc": "Please enter appearance description",
"insufficientBalance": "Insufficient Balance\n\n{error}",
"designFailed": "AI Design Failed: {error}",
"addFailed": "Add Failed: {error}",
"aiDesignPlaceholderNew": "e.g. A 20-year-old female mage, blonde hair, blue eyes...",
"aiDesignPlaceholderSub": "e.g. Changed into black combat gear...",
"aiTipNew": "Describe the character, AI will generate details",
"aiTipSub": "Describe the new state, AI will generate sub-appearance description",
"nameLabel": "Character Name",
"namePlaceholder": "Enter name...",
"descLabel": "Appearance Description",
"descPlaceholder": "Enter description...",
"selectLabel": "Select Character",
"selectPlaceholder": "-- Select Character --",
"existingAppearances": "Existing:",
"reasonLabel": "Change Reason",
"reasonPlaceholder": "e.g. After changing clothes, Injured...",
"reasonTip": "Briefly describe the difference from primary appearance",
"subDescPlaceholder": "Describe only the changes...",
"subDescTip": "Only describe changes (clothes, state), face/body inherits from primary",
"adding": "Adding...",
"insufficientBalanceDefault": "Insufficient balance, please top up to continue",
"addFailedGeneric": "Add Failed",
"appearancesCount": "Appearances",
"addCharacter": "Add Character",
"addLocation": "Add Location",
"aiDesignTip": "Describe the scene you want, AI will generate name and details",
"designing": "AI designing...",
"saveName": "Save Name",
"saveOnly": "Save Only",
"sceneDescription": "Scene Description",
"scenePrompt": "Scene Description Prompt",
"appearancePrompt": "Appearance Description Prompt",
"smartModify": "Smart Modify",
"modifyPlaceholder": "e.g.: Change to night, add moonlight, add curtains...",
"modifyPlaceholderCharacter": "e.g.: Change hair to blonde, height to 180cm, wear black suit...",
"modifying": "Smart modifying...",
"modifyFailed": "Modification failed",
"editCharacter": "Edit Character",
"editLocation": "Edit Location",
"saveAndGenerate": "Save and Generate",
"generatingAutoClose": "Generating image, will close automatically when done...",
"aiLocationTip": "Enter what you want to modify, AI will adjust the scene description",
"aiDesignPlaceholderLocation": "e.g. An ancient magical library, towering bookshelves, dim candlelight, mysterious atmosphere...",
"artStyle": "Art Style",
"generate": "Generate",
"introduction": "Character Introduction",
"introductionPlaceholder": "e.g.: The protagonist; 'I' refers to her. Others call her 'Snow' or 'Sister Snow'...",
"introductionTip": "Describe the character's role in the story, narrative perspective (who 'I' refers to), how others address them",
"saveIntroduction": "Save Introduction"
},
"toolbar": {
"filter": "Filter",
"viewAll": "View All",
"showGenerated": "Generated",
"showPending": "Pending",
"assetManagement": "Asset Management",
"assetCount": "{total} assets ({appearances} character appearances + {locations} locations)",
"globalAnalyze": "Global Analysis",
"globalAnalyzing": "Performing global asset analysis...",
"globalAnalyzingHint": "Please don't refresh. Results will appear automatically when complete",
"globalAnalyzingTip": "Analyzing all episodes, extracting characters and locations...",
"globalAnalyzeHint": "Analyze all episodes to extract characters and locations",
"globalAnalyzeSuccess": "Global analysis complete: {characters} new characters, {locations} new locations",
"globalAnalyzeFailed": "Global analysis failed",
"generateAll": "Generate All Images",
"generateAllNoop": "All assets already have images, nothing to generate",
"generating": "Generating ({current}/{total})",
"regenerateAll": "Regenerate All",
"regenerateAllConfirm": "Regenerate images for all assets? This will overwrite existing images.",
"noAssetsToGenerate": "No assets available for generation",
"regenerateAllHint": "Regenerate all asset images (overwrite existing)",
"downloadAll": "Download all images as ZIP"
},
"common": {
"actions": "Actions",
"add": "Add",
"cancel": "Cancel",
"confirm": "Confirm",
"copy": "Copy",
"delete": "Delete",
"download": "Download",
"edit": "Edit",
"generate": "Generate",
"generateFailed": "Generation Failed",
"loading": "Loading...",
"none": "None",
"preview": "Preview",
"refresh": "Refresh",
"regenerate": "Regenerate",
"save": "Save",
"status": "Status",
"submitFailed": "Submit Failed",
"upload": "Upload",
"unknownError": "Unknown error"
},
"video": {
"panelCard": {
"generating": "Generating...",
"editPrompt": "Edit Prompt"
}
},
"smartImport": {
"preview": {
"saving": "Saving..."
}
},
"storyboard": {
"group": {
"generating": "Generating..."
}
},
"errors": {
"saveFailed": "Save Failed, please retry",
"failed": "failed, please retry",
"insufficientBalance": "Insufficient balance",
"aiDesignFailed": "AI design failed",
"createFailed": "Creation failed"
},
"assetLibrary": {
"button": "Asset Library",
"title": "Asset Library",
"copySuccessCharacter": "Character appearance copied successfully",
"copySuccessLocation": "Location image copied successfully",
"copySuccessVoice": "Voice copied successfully",
"copyFailed": "Copy failed: {error}",
"downloadEmpty": "No image assets to download",
"downloadFailed": "Download failed"
},
"tts": {
"voiceDesignSaved": "AI-designed voice has been set for {name}",
"saveVoiceDesignFailed": "Failed to save voice design: {error}",
"title": "Voice",
"noVoice": "No voice",
"previewFailed": "Preview failed: {error}",
"uploadFailed": "Upload audio failed: {error}",
"uploadQwenHint": "Uploaded voices can only be synthesized with IndexTTS, not QwenTTS. QwenTTS requires an AI-designed voice.",
"uploading": "Uploading...",
"uploaded": "Uploaded",
"uploadAudio": "Upload Audio",
"pause": "Pause",
"preview": "Preview Voice"
},
"characterProfile": {
"importance": {
"S": "S-Level - Main Protagonist",
"A": "A-Level - Core Supporting",
"B": "B-Level - Important Supporting",
"C": "C-Level - Minor Character",
"D": "D-Level - Extra"
},
"costumeLevel": {
"5": "Royal/Luxury",
"4": "Noble/Elite",
"3": "Professional/Quality",
"2": "Casual/Normal",
"1": "Plain/Uniform"
},
"importanceLevel": "Character Importance Level",
"characterArchetype": "Character Archetype",
"archetypePlaceholder": "e.g.: Domineering CEO, Schemer",
"personalityTags": "Personality Tags",
"addTagPlaceholder": "Add tag",
"costumeLevelLabel": "Costume Level",
"suggestedColors": "Suggested Colors",
"colorPlaceholder": "e.g.: Navy blue, Gold",
"primaryMarker": "Primary Identifier",
"markerNote": "(Recommended for S/A level)",
"markingsPlaceholder": "e.g.: Tear-shaped mole, Silver earring",
"visualKeywords": "Visual Keywords",
"keywordsPlaceholder": "e.g.: Elite aura, Ascetic style",
"editDialogTitle": "Edit Character Profile - {name}",
"confirmAndGenerate": "Confirm & Generate",
"useExisting": "Use Existing",
"editProfile": "Edit Profile",
"delete": "Delete Character",
"summary": {
"gender": "Gender:",
"age": "Age:",
"era": "Era:",
"class": "Class:",
"occupation": "Occupation:",
"personality": "Personality:",
"costume": "Costume:",
"identifier": "Identifier:"
},
"parseFailed": "Failed to parse profile data",
"confirmSuccessGenerating": "✓ Profile confirmed. Visual description generation started",
"confirmFailed": "Confirm failed: {error}",
"noPendingCharacters": "No pending characters to confirm",
"batchConfirmPrompt": "Generate visual descriptions for {count} characters?",
"batchConfirmSuccess": "✓ Visual descriptions generated for {count} characters",
"batchConfirmFailed": "Batch confirmation failed: {error}",
"deleteConfirm": "Delete this character? This action cannot be undone.",
"deleteSuccess": "✓ Character deleted",
"deleteFailed": "Delete failed: {error}"
},
"imageEdit": {
"editCharacterImage": "Edit Character Image",
"editLocationImage": "Edit Location Image",
"characterLabel": "Character: {name}",
"locationLabel": "Location: {name}",
"editInstruction": "Edit Instruction",
"subtitle": "Enter an edit instruction and optionally upload reference images",
"characterPlaceholder": "Describe what you want to change, e.g.: Change hair to blonde, add glasses, change to casual clothes...",
"locationPlaceholder": "Describe what you want to change, e.g.: Add more trees, change to night scene...",
"storyboardPlaceholder": "Describe what you want to change, e.g.: Change background color, adjust character expression...",
"noAssetHint": "No assets, click \"Add Asset\" to select",
"referenceImages": "Reference Images",
"referenceImagesHint": "(optional, paste supported)",
"startEditing": "Start Editing"
}
}
================================================
FILE: messages/en/auth.json
================================================
{
"welcomeBack": "Welcome Back",
"loginTo": "Sign in to waoowaoo",
"createAccount": "Create Account",
"joinPlatform": "Join waoowaoo",
"phoneNumber": "Username",
"password": "Password",
"confirmPassword": "Confirm Password",
"phoneNumberPlaceholder": "Enter your username",
"passwordPlaceholder": "Enter your password",
"passwordMinPlaceholder": "Enter password (at least 6 characters)",
"confirmPasswordPlaceholder": "Re-enter your password",
"loginButton": "Sign In",
"loginButtonLoading": "Signing in...",
"signupButton": "Sign Up",
"signupButtonLoading": "Signing up...",
"noAccount": "Don't have an account?",
"hasAccount": "Already have an account?",
"signupNow": "Sign Up Now",
"signinNow": "Sign In Now",
"backToHome": "← Back to Home",
"loginFailed": "Login failed, please check your phone number and password",
"loginError": "An error occurred during login",
"passwordMismatch": "Passwords do not match",
"passwordTooShort": "Password must be at least 6 characters",
"signupSuccess": "Registration successful! Redirecting to login page...",
"signupFailed": "Registration failed",
"signupError": "An error occurred during registration",
"rateLimited": "Too many requests, please try again later",
"passwordStrength": {
"weak": "Strength: Weak — Use a longer password with uppercase, lowercase, numbers and symbols",
"fair": "Strength: Fair — Consider adding more complexity",
"good": "Strength: Good",
"strong": "Strength: Strong"
}
}
================================================
FILE: messages/en/billing.json
================================================
{
"transactionType": "Transaction Type",
"startDate": "Start Date",
"endDate": "End Date",
"all": "All",
"income": "Income",
"expense": "Expense",
"reset": "Reset",
"filter": "Filter",
"noRecords": "No records",
"accountRecharge": "Account Recharge",
"serviceConsumption": "Service Consumption",
"balance": "Balance",
"allTypes": "All Types"
}
================================================
FILE: messages/en/common.json
================================================
{
"appName": "waoowaoo",
"betaVersion": "Beta v{version}",
"updateNotice": {
"title": "New version available",
"subtitle": "Current v{current} · Latest v{latest}",
"description": "A newer version has been published on GitHub Releases. View the changelog to update safely.",
"releaseName": "Release",
"publishedAt": "Published",
"updateTag": "Update",
"viewRelease": "View update",
"remindLater": "Remind me later",
"close": "Close",
"openDialog": "Open update dialog",
"checkUpdate": "Check for updates",
"upToDate": "Already up to date"
},
"loading": "Loading...",
"save": "Save",
"cancel": "Cancel",
"confirm": "Confirm",
"delete": "Delete",
"edit": "Edit",
"search": "Search",
"clear": "Clear",
"close": "Close",
"back": "Back",
"next": "Next",
"previous": "Previous",
"submit": "Submit",
"reset": "Reset",
"generate": "Generate",
"regenerate": "Regenerate",
"preview": "Preview",
"download": "Download",
"upload": "Upload",
"select": "Select",
"add": "Add",
"remove": "Remove",
"refresh": "Refresh",
"expand": "Expand",
"collapse": "Collapse",
"all": "All",
"none": "None",
"success": "Success",
"error": "Error",
"warning": "Warning",
"info": "Info",
"copy": "Copy",
"paste": "Paste",
"apply": "Apply",
"autoSave": "Auto-save",
"saved": "Saved",
"episode": "Episode",
"project": "Project",
"editEpisodeName": "Edit Episode Name",
"deleteEpisode": "Delete Episode",
"deleteEpisodeConfirm": "Confirm Delete",
"newEpisode": "New Episode",
"optional": "(Optional)",
"rename": "Rename",
"dragToReorder": "Drag to reorder",
"episodeNamePlaceholder": "Enter episode name...",
"cancelSelection": "Cancel selection",
"referenceImage": "Reference image",
"previewLarge": "Preview large",
"viewOriginal": "View original",
"schemeN": "Scheme {n}",
"insufficientBalance": "Insufficient Balance",
"insufficientBalanceDetail": "Insufficient account balance, please recharge to continue",
"operationFailed": "Operation failed",
"pleaseRetry": "Please retry",
"recommended": "Recommended",
"language": {
"select": "Select language",
"zh": "Chinese",
"en": "English",
"switchConfirmTitle": "Switch language?",
"switchConfirmMessage": "Switching to {targetLanguage} will update not only interface text, but also end-to-end prompts, script generation, and workflow outputs. Continue?",
"switchConfirmAction": "Switch now"
},
"taskStatus": {
"intent": {
"generate": {
"running": {
"image": "Generating",
"video": "Generating",
"audio": "Generating",
"text": "Generating"
}
},
"regenerate": {
"running": {
"image": "Regenerating",
"video": "Regenerating",
"audio": "Regenerating",
"text": "Regenerating"
}
},
"modify": {
"running": {
"image": "Modifying",
"video": "Modifying",
"audio": "Modifying",
"text": "Modifying"
}
},
"analyze": {
"running": {
"image": "Analyzing",
"video": "Analyzing",
"audio": "Analyzing",
"text": "Analyzing"
}
},
"build": {
"running": {
"image": "Building",
"video": "Building",
"audio": "Building",
"text": "Building"
}
},
"convert": {
"running": {
"image": "Converting",
"video": "Converting",
"audio": "Converting",
"text": "Converting"
}
},
"process": {
"running": {
"image": "Processing",
"video": "Processing",
"audio": "Processing",
"text": "Processing"
}
}
},
"failed": {
"image": "Failed",
"video": "Failed",
"audio": "Failed",
"text": "Failed"
}
}
}
================================================
FILE: messages/en/configModal.json
================================================
{
"title": "Project Config",
"subtitle": "Defaults to the global settings. You can customize models for this project only — changes apply to this project only.",
"saved": "Saved",
"autoSave": "Auto-save",
"visualStyle": "Visual Style",
"modelParams": "Model Parameters",
"aspectRatio": "Aspect Ratio",
"ttsSettings": "TTS Settings",
"loadingModels": "Loading model list...",
"analysisModel": "Analysis Model",
"characterModel": "Character Model",
"locationModel": "Location Model",
"storyboardModel": "Storyboard Model",
"editModel": "Edit Model",
"videoModel": "Video Model",
"audioModel": "Speech Synthesis Model",
"videoResolution": "Video Resolution",
"ttsVoice": "TTS Voice",
"ttsRate": "Speech Rate",
"fetchModelsFailed": "Failed to fetch user model list",
"placeholder": "Please enter...",
"description": "Description",
"hint": "Hint",
"pleaseSelect": "Please select...",
"selectModel": "Select Model",
"paramConfig": "Parameters",
"fixed": "Fixed",
"noParams": "No configurable parameters",
"confirm": "Confirm",
"cancel": "Cancel",
"delete": "Delete",
"boolOn": "On",
"boolOff": "Off"
}
================================================
FILE: messages/en/errors.json
================================================
{
"UNAUTHORIZED": "Please log in first",
"FORBIDDEN": "Access denied",
"NOT_FOUND": "Resource not found",
"INSUFFICIENT_BALANCE": "Insufficient API balance. Please top up and retry",
"RATE_LIMIT": "Too many requests. Please retry in {retryAfter} seconds",
"MODEL_NOT_OPEN": "Model permission is not activated. Go to https://console.volcengine.com/ark/region:ark+cn-beijing/openManagement?LLM=%7B%7D&advancedActiveKey=model and click \"Activate all models\" in the top-right of Model Management",
"MODEL_NOT_REGISTERED": "Model is not registered. Add an available model in configuration first",
"MODEL_NOT_CONFIGURED": "No model configured. Please go to Settings and add the required model type before generating.",
"QUOTA_EXCEEDED": "Quota exceeded. Please try again later",
"GENERATION_FAILED": "Generation failed. Please retry",
"GENERATION_TIMEOUT": "Generation timed out. Please retry",
"SENSITIVE_CONTENT": "Content may contain sensitive material",
"INVALID_PARAMS": "Invalid parameters",
"MISSING_CONFIG": "Please complete model configuration first",
"INTERNAL_ERROR": "Server error. Please try again later",
"NETWORK_ERROR": "Network error. Please check your connection",
"EMPTY_RESPONSE": "Model returned an empty response (no meaningful content). Please retry",
"EXTERNAL_ERROR": "External service temporarily unavailable. Please retry later",
"TASK_NOT_READY": "Task is still processing",
"NO_RESULT": "Task has no result",
"CONFLICT": "Resource state conflict"
}
================================================
FILE: messages/en/landing.json
================================================
{
"title": "waoowaoo",
"subtitle": "AI Film & TV Studio",
"enterWorkspace": "Enter Workspace",
"loading": "Loading...",
"getStarted": "Get Started",
"learnMore": "Learn More",
"features": {
"title": "Unleash Infinite Creativity",
"subtitle": "Full-process AI assistance, from script to final cut",
"character": {
"title": "Character Workshop",
"description": "Create unique anime characters with high consistency"
},
"storyboard": {
"title": "Smart Storyboard",
"description": "One-click text to storyboard, precise narrative control"
},
"world": {
"title": "World Building",
"description": "Immersive scene generation to build grand story backgrounds"
}
},
"footer": {
"copyright": "2026 waoowaoo AI. All rights reserved."
}
}
================================================
FILE: messages/en/layout.json
================================================
{
"title": "AI Anime Production Platform",
"description": "Create professional anime content with cutting-edge AI technology"
}
================================================
FILE: messages/en/modelSection.json
================================================
{
"llmModels": "Text Model List",
"imageModels": "Image Model List",
"videoModels": "Video Model List",
"price": "Price",
"pricePerMillion": "Per million tokens",
"pricePerImage": "Per image",
"pricePerVideo": "Per video",
"name": "Name",
"modelId": "Model ID",
"modelName": "Model Name",
"provider": "Provider",
"resolution": "Resolution",
"add": "Add",
"addModel": "Add Model",
"addNewModel": "Add New Model",
"selectPreset": "Select Preset Model",
"customModel": "Custom Model",
"confirmAdd": "Confirm",
"cancel": "Cancel",
"done": "Done",
"fillComplete": "Please fill in all fields",
"noModels": "No models yet, click the button above to add",
"noApiKey": "Configure API Key",
"batchMode": "Batch",
"batchModeTooltip": "Offline inference, 50% cheaper, completes within 24 hours"
}
================================================
FILE: messages/en/nav.json
================================================
{
"workspace": "Workspace",
"assetHub": "Asset Hub",
"profile": "Settings",
"downloadLogs": "Download Logs",
"signin": "Sign In",
"signup": "Sign Up",
"logout": "Logout"
}
================================================
FILE: messages/en/novel-promotion.json
================================================
{
"stages": {
"story": "Story",
"script": "Script",
"storyboard": "Storyboard",
"video": "Video",
"editor": "AI Editor",
"editorComingSoon": "Coming soon, follow us for updates"
},
"buttons": {
"assetLibrary": "Asset Library",
"settings": "Project Config",
"refreshData": "Refresh Data",
"enterVideoGeneration": "Enter Video Generation →"
},
"smartImport": {
"title": "Start Your Creative Journey",
"subtitle": "First, choose your creation method",
"manualCreate": {
"title": "Create from Episode 1",
"description": "Start from episode 1, suitable for episodic creation or single short videos",
"button": "Start Creating"
},
"smartImport": {
"title": "Smart Import Full Book",
"description": "Upload a complete novel or script, AI engine automatically recognizes chapter structure and splits into episodes.",
"button": "Import Now",
"recommended": "Recommended"
},
"upload": {
"title": "Upload Raw Material",
"subtitle": "AI engine is ready, automatic episode splitting and formatting",
"maxWords": "(Max 30,000 words)",
"textInput": "Enter Text Content",
"documentUpload": "Upload Full Document",
"placeholder": "Paste your novel chapters or script content here...",
"filePlaceholder": "File uploaded mode",
"clickUpload": "Click to upload document",
"clearTextFirst": "Please clear left text first",
"supportedFormats": "Supports Word, TXT formats",
"preview": "Preview",
"expandPreview": "Expand More",
"collapsePreview": "Collapse",
"deleteFile": "Delete File",
"startAnalysis": "Start Smart Analysis",
"back": "Back",
"words": "words"
},
"analyzing": {
"title": "AI is Analyzing Your Story",
"description": "Recognizing chapter structure, smart splitting in progress...",
"autoSave": "Will auto-save after analysis complete"
},
"preview": {
"title": "Smart Splitting Complete",
"episodeCount": "Automatically split into {count} episodes",
"totalWords": "Total {count} words",
"autoSaved": "✓ Auto-saved",
"reanalyze": "Re-analyze",
"confirm": "Confirm Complete",
"saving": "Saving...",
"episodeList": "Episode List",
"addEpisode": "Add Episode",
"averageWords": "Average per episode",
"episodeContent": "Episode Content",
"episodePlaceholder": "Enter episode title...",
"summaryPlaceholder": "Enter plot summary...",
"newEpisode": "New Episode",
"deleteEpisode": "Delete Episode",
"deleteConfirm": {
"title": "Confirm Delete",
"message": "Are you sure you want to delete \"{title}\"?",
"cancel": "Cancel",
"confirm": "Confirm Delete"
},
"tip": {
"title": "Tip",
"content": "You can directly edit titles, summaries, and content. After clicking [Confirm Complete], episodes will be officially imported into the project"
}
},
"errors": {
"fileTooLarge": "File too large, please upload a file smaller than 10MB",
"docNotSupported": ".doc format not supported, please convert to .docx in Word",
"fileEmpty": "File content is empty",
"fileReadError": "File read failed, please try again",
"uploadFirst": "Please upload or paste content first",
"analyzeFailed": "Analysis failed",
"saveFailed": "Save failed"
},
"cancelConfirm": "Are you sure you want to cancel? Analyzed episodes will be cleared."
},
"storyInput": {
"currentEditing": "Currently editing: {name}",
"editingTip": "The following workflow is for this episode only. Switch episodes in the top left if needed",
"wordCount": "Word count:",
"assetLibraryTip": {
"title": "Need custom characters and locations?",
"description": "Click the 「Asset Library」 button in the top right to upload asset setting documents or manually add characters/locations. AI will prioritize using settings from the asset library for analysis."
},
"videoRatio": "Video Ratio",
"videoRatioHint": "Pick a ratio that matches your target platform and content format",
"ratioUsage": {
"1_1": "1:1: Square frame, good for avatars, covers and generic social posts",
"9_16": "9:16: Vertical video, ideal for TikTok, Reels, Shorts and other short‑video feeds",
"16_9": "16:9: Horizontal video, ideal for YouTube, Bilibili and desktop playback",
"4_3": "4:3: Classic TV ratio, useful for legacy footage or safe cropping",
"3_4": "3:4: Slightly vertical, suitable for mixed text + video layouts",
"2_3": "2:3: More vertical, good for posters and character key art",
"3_2": "3:2: Slightly horizontal, good for landscapes and story scenes",
"4_5": "4:5: Vertical poster ratio, common in social feed images",
"5_4": "5:4: Horizontal poster ratio, suitable for PC web banners",
"21_9": "21:9: Ultra‑wide cinematic frame, ideal for movie‑like shots and panoramas"
},
"ratioUsageTag": {
"1_1": "Square · Avatars/Covers",
"9_16": "Vertical · Short video",
"16_9": "Horizontal · Long video",
"4_3": "Horizontal · Classic TV",
"3_4": "Vertical · Text + video",
"2_3": "Vertical · Posters/Key art",
"3_2": "Horizontal · Landscape/Story",
"4_5": "Vertical · Feed image",
"5_4": "Horizontal · Banner",
"21_9": "Ultra‑wide · Cinema feel"
},
"visualStyle": "Visual Style",
"visualStyleHint": "Pick a style that matches your audience — e.g. Realistic for live‑action, Anime for 2D content",
"currentConfigSummary": "Current config: {ratio} · {style}. All subsequent generations will use this combo.",
"assetLibraryRatioNote": "Asset library ratios are not affected",
"moreConfig": "For more configuration options, click the 「 Settings」 button in the top right",
"narration": {
"title": "Enable Narration Voiceover",
"description": "Generate TTS voice narration to add commentary to your video"
},
"creating": "AI Creating...",
"ready": "✓ Configuration complete, ready for next step",
"pleaseInput": "Please enter script content first"
},
"execution": {
"selectEpisode": "Please select an episode first",
"fillContentFirst": "Please enter content first",
"requestAborted": "Request aborted (possibly due to page refresh)",
"analysisFailed": "Asset analysis failed",
"prepareFailed": "Preparation failed",
"generationFailed": "Generation failed",
"batchVideoFailed": "Batch video generation failed",
"updateFailed": "Update failed",
"saveFailed": "Save failed",
"storyToScriptRunning": "Story→Script V2 running",
"scriptToStoryboardRunning": "Script→Storyboard V2 running",
"storyToScriptFailed": "Story to script failed",
"scriptToStoryboardFailed": "Script to storyboard failed",
"taskStreamTimeout": "Task timed out. The task may still be running in the background — please check its status or retry"
},
"rebuildConfirm": {
"storyToScript": {
"title": "Script Flow Will Be Rebuilt",
"message": "Downstream storyboard data is detected for this episode ({storyboardCount} storyboards, {panelCount} panels). Continuing will clear and rebuild this data. Continue?"
},
"scriptToStoryboard": {
"title": "Storyboard Data Will Be Rebuilt",
"message": "Existing storyboard data is detected for this episode ({storyboardCount} storyboards, {panelCount} panels). Continuing will clear current storyboards and regenerate them. Continue?"
},
"confirm": "Continue and Clear",
"cancel": "Cancel"
}
}
================================================
FILE: messages/en/profile.json
================================================
{
"user": "User",
"personalAccount": "Personal Account",
"availableBalance": "Available Balance",
"openSourceNoBilling": "Open-source edition, no billing required",
"frozen": "Frozen",
"totalSpent": "Total Spent",
"apiConfig": "API Configuration",
"rechargeRecords": "Recharge Records",
"billingRecords": "Billing Records",
"logout": "Logout",
"downloadLogs": "Download Logs",
"accountTransactions": "Account Transactions",
"projectDetails": "Project Details",
"summary": "Summary",
"transactions": "Transactions",
"noTransactions": "No transaction records",
"noProjectCosts": "No project cost records",
"noDetails": "This project has no cost details",
"noRecords": "No records",
"byType": "By Type",
"byAction": "By Action",
"times": "times",
"total": "Total",
"filter": "Filter",
"allTypes": "All Types",
"recharge": "Account Recharge",
"consume": "Service Consumption",
"balanceAfter": "Balance {amount}",
"recordCount": "{count} records",
"totalCost": "Total {amount}",
"previousPage": "Previous",
"nextPage": "Next",
"pagination": "{total} items, Page {page} / {totalPages}",
"episodeLabel": "Episode {number}",
"billingDetail": {
"imageWithRes": "{count} images · {resolution}",
"image": "{count} images",
"videoWithRes": "{count} videos · {resolution}",
"video": "{count} videos",
"tokens": "{count} tokens",
"seconds": "{count}s",
"calls": "{count} calls"
},
"apiTypes": {
"image": "Image Generation",
"video": "Video Generation",
"text": "Text Analysis",
"tts": "Text-to-Speech",
"voice": "Voice Acting",
"voice_design": "Voice Design",
"lip_sync": "Lip Sync"
},
"actionTypes": {
"image_panel": "Storyboard Image",
"image_character": "Character Image",
"image_location": "Location Image",
"video_panel": "Video Generation",
"lip_sync": "Lip Sync",
"voice_line": "Voice Synthesis",
"voice_design": "Voice Design",
"asset_hub_voice_design": "Asset Hub Voice Design",
"regenerate_storyboard_text": "Regenerate Storyboard Text",
"insert_panel": "Insert Panel",
"panel_variant": "Shot Variant",
"modify_asset_image": "Modify Image",
"regenerate_group": "Batch Regenerate",
"asset_hub_image": "Asset Hub Image",
"asset_hub_modify": "Asset Hub Modify Image",
"analyze_novel": "Novel Analysis",
"story_to_script_run": "Story to Script",
"script_to_storyboard_run": "Script to Storyboard",
"clips_build": "Clips Build",
"screenplay_convert": "Screenplay Convert",
"voice_analyze": "Voice Analysis",
"analyze_global": "Global Analysis",
"ai_modify_appearance": "AI Modify Appearance",
"ai_modify_location": "AI Modify Location",
"ai_modify_shot_prompt": "AI Modify Shot Prompt",
"analyze_shot_variants": "Analyze Shot Variants",
"ai_create_character": "AI Create Character",
"ai_create_location": "AI Create Location",
"reference_to_character": "Reference to Character",
"character_profile_confirm": "Confirm Character Profile",
"character_profile_batch_confirm": "Batch Confirm Character Profiles",
"episode_split_llm": "Episode Split",
"asset_hub_ai_design_character": "Asset Hub AI Design Character",
"asset_hub_ai_design_location": "Asset Hub AI Design Location",
"asset_hub_ai_modify_character": "Asset Hub AI Modify Character",
"asset_hub_ai_modify_location": "Asset Hub AI Modify Location",
"asset_hub_reference_to_character": "Asset Hub Reference to Character",
"storyboard": "Storyboard",
"storyboard_candidate": "Storyboard Candidate",
"character": "Character Image",
"location": "Location Image",
"video": "Video",
"analyze": "Analysis",
"analyze_character": "Character Analysis",
"analyze_location": "Location Analysis",
"clips": "Clip Splitting",
"storyboard_text_plan": "Storyboard Planning",
"storyboard_text_detail": "Storyboard Detail",
"tts": "TTS",
"regenerate": "Regenerate",
"voice-generate": "Voice Generation",
"voice-design": "Voice Design",
"lip-sync": "Lip Sync"
}
}
================================================
FILE: messages/en/progress.json
================================================
{
"analyzing": "Analyzing story structure...",
"splittingClips": "Splitting into clips...",
"convertingScreenplay": "Converting to screenplay...",
"submittingStoryboard": "Submitting storyboard...",
"step": "Step {current} of {total}",
"status": {
"completed": "Completed",
"failed": "Failed",
"processing": "Processing",
"queued": "Queued",
"pending": "Pending"
},
"stageCard": {
"stage": "Stage",
"realtimeStream": "Realtime Stream",
"currentStage": "Current Stage",
"outputTitle": "Live AI Output · {stage}",
"waitingModelOutput": "Waiting for model output...",
"reasoningNotProvided": "No reasoning was returned for this step"
},
"runtime": {
"waitingExecution": "Waiting to start",
"taskCreated": "Task created",
"taskStarted": "Task started",
"taskCompleted": "Task completed",
"taskFailed": "Task failed",
"taskProcessing": "Task processing...",
"llm": {
"processing": "Model is processing...",
"output": "Model is generating output...",
"reasoning": "Model is reasoning...",
"completed": "Model output completed",
"failed": "Model output failed"
},
"stage": {
"llmSubmit": "Submitting model request",
"llmStreaming": "Model streaming output",
"llmFallbackNonStream": "Model fallback to non-stream mode",
"llmCompleted": "Model output completed",
"llmFailed": "Model output failed"
}
},
"taskType": {
"generic": "Task",
"imagePanel": "Storyboard image",
"imageCharacter": "Character image",
"imageLocation": "Location image",
"videoPanel": "Video generation",
"lipSync": "Lip sync",
"voiceLine": "Voice generation",
"voiceDesign": "Voice design",
"assetHubVoiceDesign": "Asset hub voice design",
"regenerateStoryboardText": "Regenerate storyboard text",
"insertPanel": "Insert storyboard panel",
"panelVariant": "Storyboard variant",
"modifyAssetImage": "Image edit",
"regenerateGroup": "Batch regenerate",
"assetHubImage": "Asset hub image",
"assetHubModify": "Asset hub edit",
"analyzeNovel": "Content analysis",
"storyToScriptRun": "Story to script",
"scriptToStoryboardRun": "Script to storyboard",
"clipsBuild": "Clip generation",
"screenplayConvert": "Screenplay conversion",
"voiceAnalyze": "Voice line analysis",
"analyzeGlobal": "Global analysis",
"aiModifyAppearance": "Character description modify",
"aiModifyLocation": "Location description modify",
"aiModifyShotPrompt": "Shot prompt modify",
"analyzeShotVariants": "Shot variant analysis",
"aiCreateCharacter": "Project character design",
"aiCreateLocation": "Project location design",
"referenceToCharacter": "Reference to character",
"characterProfileConfirm": "Character profile confirm",
"characterProfileBatchConfirm": "Character profile batch confirm",
"episodeSplitLlm": "Smart episode split",
"assetHubAiDesignCharacter": "Asset hub character design",
"assetHubAiDesignLocation": "Asset hub location design",
"assetHubAiModifyCharacter": "Asset hub character modify",
"assetHubAiModifyLocation": "Asset hub location modify",
"assetHubReferenceToCharacter": "Asset hub reference to character"
},
"stage": {
"received": "Task received",
"generateCharacterImage": "Generate character image",
"generateLocationImage": "Generate location image",
"generatePanelCandidate": "Generate panel candidate",
"generatePanelVideo": "Generate panel video",
"generateVoiceSubmit": "Submit voice task",
"generateVoicePersist": "Persist voice result",
"voiceDesignSubmit": "Submit voice design task",
"voiceDesignDone": "Voice design completed",
"submitLipSync": "Submit lip sync task",
"persistLipSync": "Persist lip sync result",
"storyboardClip": "Generate storyboard clip",
"regenerateStoryboardPrepare": "Prepare storyboard regeneration",
"regenerateStoryboardPersist": "Persist storyboard regeneration",
"storyToScriptPrepare": "Prepare story-to-script parameters",
"storyToScriptStep": "Execute story-to-script step",
"storyToScriptPersist": "Persist story-to-script output",
"storyToScriptPersistDone": "Story-to-script output persisted",
"scriptToStoryboardPrepare": "Prepare script-to-storyboard parameters",
"scriptToStoryboardStep": "Execute script-to-storyboard step",
"scriptToStoryboardPersist": "Persist script-to-storyboard output",
"scriptToStoryboardPersistDone": "Storyboard and voice output persisted",
"insertPanelGenerateText": "Generate inserted panel text",
"insertPanelPersist": "Persist inserted panel",
"pollingExternal": "Waiting for external service",
"enqueueFailed": "Task enqueue failed",
"llmProxySubmit": "Submit LLM task",
"llmProxyExecute": "Execute LLM task",
"llmProxyPersist": "Persist LLM result"
},
"runConsole": {
"storyToScript": "Story to Script",
"scriptToStoryboard": "Script to Storyboard",
"storyToScriptRunning": "Story→Script running",
"scriptToStoryboardRunning": "Script→Storyboard running",
"storyToScriptSubtitle": "Story To Script V2",
"scriptToStoryboardSubtitle": "Script To Storyboard V2",
"stop": "Stop",
"minimize": "Minimize"
},
"streamStep": {
"analyzeCharacters": "Analyze characters",
"analyzeLocations": "Analyze locations",
"splitClips": "Split clips",
"screenplayConversion": "Convert screenplay",
"storyboardPlan": "Plan storyboard",
"cinematographyRules": "Generate cinematography rules",
"actingDirection": "Generate acting direction",
"storyboardDetailRefine": "Refine storyboard details",
"voiceAnalyze": "Analyze voice lines"
}
}
================================================
FILE: messages/en/providerSection.json
================================================
{
"addProvider": "+ Add Provider",
"name": "Name",
"add": "Add",
"save": "Save",
"fillRequired": "Please fill in required fields"
}
================================================
FILE: messages/en/scriptView.json
================================================
{
"title": "Script View",
"scriptBreakdown": "Script Breakdown",
"splitCount": "{count} clips split",
"noClips": "No clips yet, please generate from story view",
"segment": {
"title": "Clip {index}",
"selected": "(Selected)"
},
"inSceneAssets": "In-Scene Assets",
"currentSelected": "Selected: Clip {number}",
"assetView": {
"allClips": "All Clips",
"viewingClip": "Viewing Clip {number}"
},
"asset": {
"generateCharacter": "Click to generate character →",
"generateLocation": "Click to generate location →",
"removeCharacterConfirm": "Are you sure you want to remove this character from current clip?",
"removeLocationConfirm": "Are you sure you want to remove this location from current clip?",
"removeFromClip": "Remove from current clip",
"noAudio": "No audio",
"playing": "Playing",
"listen": "Listen",
"activeCharacters": "Active Characters",
"activeLocations": "Active Locations",
"selectCharacter": "Select character/appearance to add",
"selectLocation": "Select location to add",
"loadingAssets": "Loading assets...",
"appearanceCount": "{count} appearances",
"added": "Added",
"primary": "Primary",
"subAppearance": "Sub appearance",
"defaultAppearance": "Default appearance",
"clickToRemove": "Click to remove {name}",
"clickToAdd": "Click to add {name}"
},
"screenplay": {
"scene": "Scene {number}",
"location": "Location:",
"locationTime": "Time:",
"day": "Day",
"night": "Night",
"dawn": "Dawn",
"dusk": "Dusk",
"dialogue": "Dialogue",
"action": "Action",
"narration": "Narration",
"content": "Original Content",
"noContent": "No content yet",
"clickToEdit": "Click to edit",
"interior": "INT",
"exterior": "EXT",
"characters": "Characters",
"noCharacter": "No character info",
"noLocation": "No active locations",
"noCharacterInClip": "No active characters"
},
"confirm": {
"removeCharacter": "Are you sure you want to remove this character from current clip?",
"removeLocation": "Are you sure you want to remove this location from current clip?"
},
"generate": {
"missingAssets": "{count} assets missing images",
"missingAssetsTip": "Please generate images for all characters and locations in",
"missingAssetsTipLink": "first",
"generating": "Generating...",
"startGenerate": "Confirm and Start Drawing →"
}
}
================================================
FILE: messages/en/smartImport.json
================================================
{
"title": "Start Your Creative Journey",
"subtitle": "First, choose your creation method",
"manualCreate": {
"title": "Create from Episode 1",
"description": "Start from episode 1, suitable for episodic creation or single short videos",
"button": "Start Creating"
},
"manualDesc": "Start from the first episode, suitable for serialized or single short video production",
"startCreate": "Start Creating",
"smartImport": {
"title": "Smart Text Split",
"description": "Upload a complete novel or script, AI engine automatically recognizes chapter structure and splits into episodes.",
"button": "Import Now",
"recommended": "Recommended"
},
"markerDetection": {
"enable": "Use Markers (Episode X / 第X集)",
"tooltip": "Auto-detect [Episode X], [Chapter X], [第X集/章] markers, free & fast"
},
"smartImportDesc": "Upload your novel or script, AI engine automatically identifies chapter structure for one-click smart episode splitting.",
"recommended": "Recommended",
"importNow": "Import Now",
"uploadTitle": "Upload Source Material",
"uploadSubtitle": "AI engine ready, one-click auto-split and format",
"maxWords": "Max 30,000 words",
"textInput": "Enter Text Content",
"textPlaceholder": "Paste your novel chapter or script content here...",
"uploadDoc": "Upload Complete Document",
"clickUpload": "Click to Upload",
"clearText": "Please clear left text first",
"supportFormat": "Supports Word, TXT formats",
"fileMax": "Max 30,000 words",
"words": "words",
"startAnalyzing": "Start Analysis",
"analyzing": {
"title": "AI is Analyzing Your Story",
"description": "Recognizing chapter structure, smart splitting in progress...",
"autoSave": "Will auto-save after analysis complete"
},
"analyzingDesc": "Identifying chapter structure, smart splitting...",
"autoSave": "Will auto-save after analysis",
"splitComplete": "Smart Split Complete",
"splitResult": "Auto-split into {count} episodes, total {words} words",
"saved": "Auto-saved",
"reAnalyze": "Re-analyze",
"confirmComplete": "Confirm Complete",
"saving": "Saving...",
"episodeList": "Episode List",
"episodes": "episodes",
"episode": "Episode {num}",
"addEpisode": "Add Episode",
"newEpisode": "New Episode",
"avgWords": "Average per episode",
"episodeContent": "Episode Content",
"plotSummary": "Plot Summary",
"enterTitle": "Enter episode title...",
"enterSummary": "Enter plot summary...",
"confirmDelete": "Confirm Delete",
"deleteConfirmMsg": "Are you sure you want to delete \"{title}\"?",
"preview": {
"title": "Smart Splitting Complete",
"episodeCount": "Automatically split into {count} episodes",
"totalWords": "Total {count} words",
"autoSaved": "✓ Auto-saved",
"reanalyze": "Re-analyze",
"confirm": "Confirm Complete",
"saving": "Saving...",
"episodeList": "Episode List",
"addEpisode": "Add Episode",
"averageWords": "Average per episode",
"episodeContent": "Episode Content",
"episodePlaceholder": "Enter episode title...",
"summaryPlaceholder": "Enter plot summary...",
"newEpisode": "New Episode",
"deleteEpisode": "Delete Episode",
"deleteConfirm": {
"title": "Confirm Delete",
"message": "Are you sure you want to delete \"{title}\"?",
"cancel": "Cancel",
"confirm": "Confirm Delete"
},
"tip": {
"title": "Tip",
"content": "You can directly edit titles, summaries, and content. After clicking [Confirm Complete], episodes will be officially imported into the project"
}
},
"collapsePreview": "Collapse Preview",
"expandMore": "Expand More",
"deleteFile": "Delete File",
"fileTooLarge": "File size cannot exceed 10MB",
"docNotSupported": ".doc format not supported. Please save as .docx or .txt and try again",
"fileEmpty": "File content is empty",
"fileReadError": "File read failed, please ensure correct format",
"uploadFirst": "Please upload a file or paste text first",
"analyzeFailed": "Analysis failed",
"saveFailed": "Save failed",
"cancelConfirm": "Are you sure you want to cancel? Analyzed episodes will be cleared.",
"deleteEpisode": "Delete Episode",
"upload": {
"title": "Upload Raw Material",
"subtitle": "AI engine is ready, automatic episode splitting and formatting",
"maxWords": "(Max 30,000 words)",
"textInput": "Enter Text Content",
"documentUpload": "Uploa
gitextract_4hr0t8jh/ ├── .dockerignore ├── .eslintrc.json ├── .gitignore ├── .husky/ │ ├── pre-commit │ └── pre-push ├── .nvmrc ├── .tmp_check_task.ts ├── CHANGELOG.md ├── Dockerfile ├── README.md ├── README_en.md ├── caddyfile ├── debug-request.json ├── docker-compose.test.yml ├── docker-compose.yml ├── eslint.config.mjs ├── extract_chinese.py ├── lib/ │ └── prompts/ │ ├── character-reference/ │ │ ├── character_image_to_description.en.txt │ │ ├── character_image_to_description.zh.txt │ │ ├── character_reference_to_sheet.en.txt │ │ └── character_reference_to_sheet.zh.txt │ ├── novel-promotion/ │ │ ├── agent_acting_direction.en.txt │ │ ├── agent_acting_direction.zh.txt │ │ ├── agent_character_profile.en.txt │ │ ├── agent_character_profile.zh.txt │ │ ├── agent_character_visual.en.txt │ │ ├── agent_character_visual.zh.txt │ │ ├── agent_cinematographer.en.txt │ │ ├── agent_cinematographer.zh.txt │ │ ├── agent_clip.en.txt │ │ ├── agent_clip.zh.txt │ │ ├── agent_shot_variant_analysis.en.txt │ │ ├── agent_shot_variant_analysis.zh.txt │ │ ├── agent_shot_variant_generate.en.txt │ │ ├── agent_shot_variant_generate.zh.txt │ │ ├── agent_storyboard_detail.en.txt │ │ ├── agent_storyboard_detail.zh.txt │ │ ├── agent_storyboard_insert.en.txt │ │ ├── agent_storyboard_insert.zh.txt │ │ ├── agent_storyboard_plan.en.txt │ │ ├── agent_storyboard_plan.zh.txt │ │ ├── character_create.en.txt │ │ ├── character_create.zh.txt │ │ ├── character_description_update.en.txt │ │ ├── character_description_update.zh.txt │ │ ├── character_modify.en.txt │ │ ├── character_modify.zh.txt │ │ ├── character_regenerate.en.txt │ │ ├── character_regenerate.zh.txt │ │ ├── episode_split.en.txt │ │ ├── episode_split.zh.txt │ │ ├── image_prompt_modify.en.txt │ │ ├── image_prompt_modify.zh.txt │ │ ├── location_create.en.txt │ │ ├── location_create.zh.txt │ │ ├── location_description_update.en.txt │ │ ├── location_description_update.zh.txt │ │ ├── location_modify.en.txt │ │ ├── location_modify.zh.txt │ │ ├── location_regenerate.en.txt │ │ ├── location_regenerate.zh.txt │ │ ├── screenplay_conversion.en.txt │ │ ├── screenplay_conversion.zh.txt │ │ ├── select_location.en.txt │ │ ├── select_location.zh.txt │ │ ├── single_panel_image.en.txt │ │ ├── single_panel_image.zh.txt │ │ ├── storyboard_edit.en.txt │ │ ├── storyboard_edit.zh.txt │ │ ├── voice_analysis.en.txt │ │ └── voice_analysis.zh.txt │ ├── proxy.ts │ └── skills/ │ ├── api-config-template.system.txt │ └── tutorial.system.txt ├── messages/ │ ├── en/ │ │ ├── actions.json │ │ ├── apiConfig.json │ │ ├── apiTypes.json │ │ ├── assetHub.json │ │ ├── assetLibrary.json │ │ ├── assetModal.json │ │ ├── assetPicker.json │ │ ├── assets.json │ │ ├── auth.json │ │ ├── billing.json │ │ ├── common.json │ │ ├── configModal.json │ │ ├── errors.json │ │ ├── landing.json │ │ ├── layout.json │ │ ├── modelSection.json │ │ ├── nav.json │ │ ├── novel-promotion.json │ │ ├── profile.json │ │ ├── progress.json │ │ ├── providerSection.json │ │ ├── scriptView.json │ │ ├── smartImport.json │ │ ├── stages.json │ │ ├── storyboard.json │ │ ├── video.json │ │ ├── voice.json │ │ ├── workspace.json │ │ ├── workspaceDetail.json │ │ └── worldContextModal.json │ └── zh/ │ ├── actions.json │ ├── apiConfig.json │ ├── apiTypes.json │ ├── assetHub.json │ ├── assetLibrary.json │ ├── assetModal.json │ ├── assetPicker.json │ ├── assets.json │ ├── auth.json │ ├── billing.json │ ├── common.json │ ├── configModal.json │ ├── errors.json │ ├── landing.json │ ├── layout.json │ ├── modelSection.json │ ├── nav.json │ ├── novel-promotion.json │ ├── profile.json │ ├── progress.json │ ├── providerSection.json │ ├── scriptView.json │ ├── smartImport.json │ ├── stages.json │ ├── storyboard.json │ ├── video.json │ ├── voice.json │ ├── workspace.json │ ├── workspaceDetail.json │ └── worldContextModal.json ├── middleware.ts ├── next.config.ts ├── package.json ├── postcss.config.mjs ├── prisma/ │ ├── schema.prisma │ └── schema.sqlit.prisma ├── scripts/ │ ├── billing-cleanup-pending-freezes.ts │ ├── billing-reconcile-ledger.ts │ ├── bull-board.ts │ ├── check-api-handler.ts │ ├── check-capability-catalog.mjs │ ├── check-image-urls-contract.ts │ ├── check-log-semantic.ts │ ├── check-media-normalization.ts │ ├── check-model-config-contract.mjs │ ├── check-no-console.ts │ ├── check-outbound-image-runtime-sample.ts │ ├── check-outbound-image-success-rate.ts │ ├── check-outbound-image-unification.ts │ ├── check-pricing-catalog.mjs │ ├── cleanup-remove-legacy-voice-data.ts │ ├── diagnose-project.ts │ ├── guards/ │ │ ├── api-route-contract-guard.mjs │ │ ├── changed-file-test-impact-guard.mjs │ │ ├── file-line-count-guard.mjs │ │ ├── image-reference-normalization-guard.mjs │ │ ├── locale-navigation-guard.mjs │ │ ├── no-api-direct-llm-call.mjs │ │ ├── no-duplicate-endpoint-entry.mjs │ │ ├── no-hardcoded-model-capabilities.mjs │ │ ├── no-internal-task-sync-fallback.mjs │ │ ├── no-media-provider-bypass.mjs │ │ ├── no-model-key-downgrade.mjs │ │ ├── no-multiple-sources-of-truth.mjs │ │ ├── no-provider-guessing.mjs │ │ ├── no-server-mirror-state.mjs │ │ ├── prompt-ab-regression.mjs │ │ ├── prompt-i18n-guard.mjs │ │ ├── prompt-json-canary-guard.mjs │ │ ├── prompt-semantic-regression.mjs │ │ ├── task-loading-baseline.json │ │ ├── task-loading-guard.mjs │ │ ├── task-state-unification-guard.sh │ │ ├── task-status-cutover-audit.sh │ │ ├── task-submit-compensation-guard.mjs │ │ ├── task-target-states-no-polling-guard.mjs │ │ ├── test-behavior-quality-guard.mjs │ │ ├── test-behavior-route-coverage-guard.mjs │ │ ├── test-behavior-tasktype-coverage-guard.mjs │ │ ├── test-route-coverage-guard.mjs │ │ └── test-tasktype-coverage-guard.mjs │ ├── media-archive-legacy-refs.ts │ ├── media-backfill-refs.ts │ ├── media-build-unreferenced-index.ts │ ├── media-mapping.ts │ ├── media-restore-dry-run.ts │ ├── media-safety-backup.ts │ ├── migrate-cancelled-to-failed.ts │ ├── migrate-image-urls-contract.ts │ ├── migrate-local-to-minio.ts │ ├── migrate-to-minio.sh │ ├── migrate-to-minio.ts │ ├── migrations/ │ │ ├── migrate-capability-selections.ts │ │ ├── migrate-custom-pricing-v2.ts │ │ ├── migrate-gateway-route-openai-compat.ts │ │ ├── migrate-graph-artifacts-unique-index.ts │ │ ├── migrate-model-config-contract.ts │ │ ├── migrate-qwen-to-bailian.ts │ │ ├── migrate-release-blockers.ts │ │ └── reports/ │ │ ├── model-config-migration-report.apply.json │ │ ├── model-config-migration-report.post-alias-apply.json │ │ ├── model-config-migration-report.post-alias-dryrun.json │ │ └── model-config-migration-report.pre-apply.json │ ├── task-error-stats.ts │ ├── test-full-image-flow.ts │ ├── test-image-url-flow.ts │ ├── test-minio.ts │ ├── test-regression-runner.sh │ ├── test-sign-api.ts │ ├── tmp-cleanup-project-models.mjs │ ├── tmp-find-old-model.mjs │ └── watchdog.ts ├── src/ │ ├── app/ │ │ ├── [locale]/ │ │ │ ├── auth/ │ │ │ │ ├── signin/ │ │ │ │ │ └── page.tsx │ │ │ │ └── signup/ │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ ├── profile/ │ │ │ │ ├── components/ │ │ │ │ │ ├── ApiConfigTab.tsx │ │ │ │ │ ├── api-config/ │ │ │ │ │ │ ├── DefaultModelSection.tsx │ │ │ │ │ │ ├── ProviderCard.tsx │ │ │ │ │ │ ├── ProviderSection.tsx │ │ │ │ │ │ ├── hooks.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── provider-card/ │ │ │ │ │ │ │ ├── ModelTemplateAssistantModal.tsx │ │ │ │ │ │ │ ├── ProviderAdvancedFields.tsx │ │ │ │ │ │ │ ├── ProviderBaseFields.tsx │ │ │ │ │ │ │ ├── ProviderCardShell.tsx │ │ │ │ │ │ │ ├── hooks/ │ │ │ │ │ │ │ │ └── useProviderCardState.ts │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ └── api-config-tab/ │ │ │ │ │ ├── ApiConfigProviderList.tsx │ │ │ │ │ ├── ApiConfigTabContainer.tsx │ │ │ │ │ ├── ApiConfigToolbar.tsx │ │ │ │ │ ├── DefaultModelCards.tsx │ │ │ │ │ └── hooks/ │ │ │ │ │ └── useApiConfigFilters.ts │ │ │ │ └── page.tsx │ │ │ ├── providers.tsx │ │ │ └── workspace/ │ │ │ ├── [projectId]/ │ │ │ │ ├── components/ │ │ │ │ │ └── Sidebar.tsx │ │ │ │ ├── episode-selection.ts │ │ │ │ ├── hooks/ │ │ │ │ │ └── useProject.ts │ │ │ │ ├── modes/ │ │ │ │ │ └── novel-promotion/ │ │ │ │ │ ├── NovelPromotionWorkspace.tsx │ │ │ │ │ ├── StageNavigation.tsx │ │ │ │ │ ├── WorkspaceProvider.tsx │ │ │ │ │ ├── WorkspaceStageRuntimeContext.tsx │ │ │ │ │ ├── components/ │ │ │ │ │ │ ├── AssetLibrary.tsx │ │ │ │ │ │ ├── AssetsStage.tsx │ │ │ │ │ │ ├── ConfigStage.tsx │ │ │ │ │ │ ├── NovelInputStage.tsx │ │ │ │ │ │ ├── PanelEditForm.tsx │ │ │ │ │ │ ├── PromptsStage.tsx │ │ │ │ │ │ ├── ScriptStage.tsx │ │ │ │ │ │ ├── ScriptView.tsx │ │ │ │ │ │ ├── SmartImportWizard.tsx │ │ │ │ │ │ ├── StoryboardStage.tsx │ │ │ │ │ │ ├── VideoStage.tsx │ │ │ │ │ │ ├── VideoStageRoute.tsx │ │ │ │ │ │ ├── VoiceStage.tsx │ │ │ │ │ │ ├── VoiceStageRoute.tsx │ │ │ │ │ │ ├── WorkspaceAssetLibraryModal.tsx │ │ │ │ │ │ ├── WorkspaceHeaderShell.tsx │ │ │ │ │ │ ├── WorkspaceRunStreamConsoles.tsx │ │ │ │ │ │ ├── WorkspaceStageContent.tsx │ │ │ │ │ │ ├── WorkspaceTopActions.tsx │ │ │ │ │ │ ├── assets/ │ │ │ │ │ │ │ ├── AddLocationModal.tsx │ │ │ │ │ │ │ ├── AssetToolbar.tsx │ │ │ │ │ │ │ ├── AssetsStageModals.tsx │ │ │ │ │ │ │ ├── AssetsStageStatusOverlays.tsx │ │ │ │ │ │ │ ├── CharacterCard.tsx │ │ │ │ │ │ │ ├── CharacterEditModal.tsx │ │ │ │ │ │ │ ├── CharacterProfileCard.tsx │ │ │ │ │ │ │ ├── CharacterProfileDialog.tsx │ │ │ │ │ │ │ ├── CharacterSection.tsx │ │ │ │ │ │ │ ├── ImageEditModal.tsx │ │ │ │ │ │ │ ├── LocationCard.tsx │ │ │ │ │ │ │ ├── LocationEditModal.tsx │ │ │ │ │ │ │ ├── LocationSection.tsx │ │ │ │ │ │ │ ├── UnconfirmedProfilesSection.tsx │ │ │ │ │ │ │ ├── VoiceSettings.tsx │ │ │ │ │ │ │ ├── character-card/ │ │ │ │ │ │ │ │ ├── CharacterCardActions.tsx │ │ │ │ │ │ │ │ ├── CharacterCardGallery.tsx │ │ │ │ │ │ │ │ └── CharacterCardHeader.tsx │ │ │ │ │ │ │ ├── hooks/ │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ ├── useAssetModals.ts │ │ │ │ │ │ │ │ ├── useAssetsCopyFromHub.ts │ │ │ │ │ │ │ │ ├── useAssetsGlobalActions.ts │ │ │ │ │ │ │ │ ├── useAssetsImageEdit.ts │ │ │ │ │ │ │ │ ├── useBatchGeneration.helpers.ts │ │ │ │ │ │ │ │ ├── useBatchGeneration.ts │ │ │ │ │ │ │ │ ├── useCharacterActions.ts │ │ │ │ │ │ │ │ ├── useLocationActions.ts │ │ │ │ │ │ │ │ ├── useProfileManagement.ts │ │ │ │ │ │ │ │ └── useTTSGeneration.ts │ │ │ │ │ │ │ └── location-card/ │ │ │ │ │ │ │ ├── LocationCardActions.tsx │ │ │ │ │ │ │ ├── LocationCardHeader.tsx │ │ │ │ │ │ │ └── LocationImageList.tsx │ │ │ │ │ │ ├── prompts-stage/ │ │ │ │ │ │ │ ├── PromptEditorPanel.tsx │ │ │ │ │ │ │ ├── PromptListCardView.tsx │ │ │ │ │ │ │ ├── PromptListPanel.tsx │ │ │ │ │ │ │ ├── PromptListTableView.tsx │ │ │ │ │ │ │ ├── PromptsStageLayout.tsx │ │ │ │ │ │ │ ├── PromptsStageShell.tsx │ │ │ │ │ │ │ ├── hooks/ │ │ │ │ │ │ │ │ └── usePromptStageActions.tsx │ │ │ │ │ │ │ └── runtime/ │ │ │ │ │ │ │ ├── hooks/ │ │ │ │ │ │ │ │ ├── usePromptAiModifyFlow.ts │ │ │ │ │ │ │ │ ├── usePromptAppendFlow.ts │ │ │ │ │ │ │ │ ├── usePromptAssetMention.ts │ │ │ │ │ │ │ │ ├── usePromptDraftByShot.ts │ │ │ │ │ │ │ │ └── usePromptEditorRuntime.ts │ │ │ │ │ │ │ ├── promptStageRuntime.types.ts │ │ │ │ │ │ │ ├── promptStageRuntime.utils.ts │ │ │ │ │ │ │ └── promptStageRuntimeCore.tsx │ │ │ │ │ │ ├── script-view/ │ │ │ │ │ │ │ ├── ScriptViewAssetsPanel.tsx │ │ │ │ │ │ │ ├── ScriptViewContainer.tsx │ │ │ │ │ │ │ ├── ScriptViewCore.tsx │ │ │ │ │ │ │ ├── ScriptViewRuntime.tsx │ │ │ │ │ │ │ ├── ScriptViewScriptPanel.tsx │ │ │ │ │ │ │ ├── SpotlightCards.tsx │ │ │ │ │ │ │ ├── asset-state-utils.ts │ │ │ │ │ │ │ └── clip-asset-utils.ts │ │ │ │ │ │ ├── smart-import/ │ │ │ │ │ │ │ ├── hooks/ │ │ │ │ │ │ │ │ └── useWizardState.ts │ │ │ │ │ │ │ ├── steps/ │ │ │ │ │ │ │ │ ├── StepConfirm.tsx │ │ │ │ │ │ │ │ ├── StepMapping.tsx │ │ │ │ │ │ │ │ ├── StepParse.tsx │ │ │ │ │ │ │ │ └── StepSource.tsx │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ ├── storyboard/ │ │ │ │ │ │ │ ├── AIDataModal.tsx │ │ │ │ │ │ │ ├── AIDataModal.types.ts │ │ │ │ │ │ │ ├── AIDataModalFormPane.tsx │ │ │ │ │ │ │ ├── AIDataModalPreviewPane.tsx │ │ │ │ │ │ │ ├── CandidateSelector.tsx │ │ │ │ │ │ │ ├── ImageEditModal.tsx │ │ │ │ │ │ │ ├── ImageEditModalAssetPicker.tsx │ │ │ │ │ │ │ ├── ImageEditModalSelectedAssets.tsx │ │ │ │ │ │ │ ├── ImageSection.css │ │ │ │ │ │ │ ├── ImageSection.tsx │ │ │ │ │ │ │ ├── ImageSectionActionButtons.tsx │ │ │ │ │ │ │ ├── ImageSectionCandidateMode.tsx │ │ │ │ │ │ │ ├── InsertPanelButton.tsx │ │ │ │ │ │ │ ├── InsertPanelModal.tsx │ │ │ │ │ │ │ ├── PanelActionButtons.tsx │ │ │ │ │ │ │ ├── PanelCard.tsx │ │ │ │ │ │ │ ├── PanelVariantModal.tsx │ │ │ │ │ │ │ ├── PanelVariantModal.types.ts │ │ │ │ │ │ │ ├── PanelVariantModalCustomOptions.tsx │ │ │ │ │ │ │ ├── PanelVariantModalSuggestionList.tsx │ │ │ │ │ │ │ ├── ScreenplayDisplay.tsx │ │ │ │ │ │ │ ├── StoryboardCanvas.tsx │ │ │ │ │ │ │ ├── StoryboardGroup.tsx │ │ │ │ │ │ │ ├── StoryboardGroup.types.ts │ │ │ │ │ │ │ ├── StoryboardGroupActions.tsx │ │ │ │ │ │ │ ├── StoryboardGroupDialogs.tsx │ │ │ │ │ │ │ ├── StoryboardGroupFailedAlert.tsx │ │ │ │ │ │ │ ├── StoryboardGroupHeader.tsx │ │ │ │ │ │ │ ├── StoryboardHeader.tsx │ │ │ │ │ │ │ ├── StoryboardPanelList.tsx │ │ │ │ │ │ │ ├── StoryboardStageShell.tsx │ │ │ │ │ │ │ ├── StoryboardToolbar.tsx │ │ │ │ │ │ │ ├── hooks/ │ │ │ │ │ │ │ │ ├── contracts.ts │ │ │ │ │ │ │ │ ├── image-generation-runtime.ts │ │ │ │ │ │ │ │ ├── panel-candidate-runtime.ts │ │ │ │ │ │ │ │ ├── panel-operations-shared.ts │ │ │ │ │ │ │ │ ├── panel-save-coordinator.ts │ │ │ │ │ │ │ │ ├── storyboard-panel-asset-utils.ts │ │ │ │ │ │ │ │ ├── storyboard-state-utils.ts │ │ │ │ │ │ │ │ ├── useAIDataModalState.ts │ │ │ │ │ │ │ │ ├── useImageGeneration.ts │ │ │ │ │ │ │ │ ├── usePanelCandidates.ts │ │ │ │ │ │ │ │ ├── usePanelCrudActions.ts │ │ │ │ │ │ │ │ ├── usePanelEpisodeCachePatch.ts │ │ │ │ │ │ │ │ ├── usePanelImageDownload.ts │ │ │ │ │ │ │ │ ├── usePanelImageModification.ts │ │ │ │ │ │ │ │ ├── usePanelImageRegeneration.ts │ │ │ │ │ │ │ │ ├── usePanelInsertActions.ts │ │ │ │ │ │ │ │ ├── usePanelOperations.ts │ │ │ │ │ │ │ │ ├── usePanelVariant.ts │ │ │ │ │ │ │ │ ├── useStoryboardAiDataRuntime.ts │ │ │ │ │ │ │ │ ├── useStoryboardBatchPanelGeneration.ts │ │ │ │ │ │ │ │ ├── useStoryboardGroupActions.ts │ │ │ │ │ │ │ │ ├── useStoryboardGroupTaskErrors.ts │ │ │ │ │ │ │ │ ├── useStoryboardInsertVariantRuntime.ts │ │ │ │ │ │ │ │ ├── useStoryboardModalRuntime.ts │ │ │ │ │ │ │ │ ├── useStoryboardPanelAssetActions.ts │ │ │ │ │ │ │ │ ├── useStoryboardStageController.ts │ │ │ │ │ │ │ │ ├── useStoryboardStageStatus.ts │ │ │ │ │ │ │ │ ├── useStoryboardStageUiState.ts │ │ │ │ │ │ │ │ ├── useStoryboardState.ts │ │ │ │ │ │ │ │ └── useStoryboardTaskAwareStoryboards.ts │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ ├── video/ │ │ │ │ │ │ │ ├── FirstLastFramePanel.tsx │ │ │ │ │ │ │ ├── VideoPanelCard.tsx │ │ │ │ │ │ │ ├── VideoPromptModal.tsx │ │ │ │ │ │ │ ├── VideoToolbar.tsx │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── panel-card/ │ │ │ │ │ │ │ │ ├── VideoPanelCardBody.tsx │ │ │ │ │ │ │ │ ├── VideoPanelCardFooter.tsx │ │ │ │ │ │ │ │ ├── VideoPanelCardHeader.tsx │ │ │ │ │ │ │ │ ├── VideoPanelCardLayout.tsx │ │ │ │ │ │ │ │ ├── VideoPanelCardShell.tsx │ │ │ │ │ │ │ │ ├── hooks/ │ │ │ │ │ │ │ │ │ └── useVideoPanelActions.tsx │ │ │ │ │ │ │ │ ├── runtime/ │ │ │ │ │ │ │ │ │ ├── hooks/ │ │ │ │ │ │ │ │ │ │ ├── usePanelLipSync.ts │ │ │ │ │ │ │ │ │ │ ├── usePanelPlayer.ts │ │ │ │ │ │ │ │ │ │ ├── usePanelPromptEditor.ts │ │ │ │ │ │ │ │ │ │ ├── usePanelTaskStatus.ts │ │ │ │ │ │ │ │ │ │ ├── usePanelVideoModel.ts │ │ │ │ │ │ │ │ │ │ └── usePanelVoiceManager.ts │ │ │ │ │ │ │ │ │ ├── shared.ts │ │ │ │ │ │ │ │ │ └── videoPanelRuntimeCore.tsx │ │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ ├── video-stage/ │ │ │ │ │ │ │ ├── VideoRenderPanel.tsx │ │ │ │ │ │ │ ├── VideoStageLayout.tsx │ │ │ │ │ │ │ ├── VideoStageShell.tsx │ │ │ │ │ │ │ ├── VideoTimelinePanel.tsx │ │ │ │ │ │ │ └── hooks/ │ │ │ │ │ │ │ └── useVideoStageRuntime.tsx │ │ │ │ │ │ ├── voice/ │ │ │ │ │ │ │ ├── EmbeddedVoiceToolbar.tsx │ │ │ │ │ │ │ ├── EmotionSettingsPanel.tsx │ │ │ │ │ │ │ ├── EmptyVoiceState.tsx │ │ │ │ │ │ │ ├── SpeakerVoiceBindingDialog.tsx │ │ │ │ │ │ │ ├── SpeakerVoiceStatus.tsx │ │ │ │ │ │ │ ├── VoiceDesignDialog.tsx │ │ │ │ │ │ │ ├── VoiceLineCard.tsx │ │ │ │ │ │ │ └── VoiceToolbar.tsx │ │ │ │ │ │ └── voice-stage/ │ │ │ │ │ │ ├── VoiceControlPanel.tsx │ │ │ │ │ │ ├── VoiceLineList.tsx │ │ │ │ │ │ ├── VoiceStageLayout.tsx │ │ │ │ │ │ ├── VoiceStageShell.tsx │ │ │ │ │ │ └── hooks/ │ │ │ │ │ │ └── useVoiceStageRuntime.tsx │ │ │ │ │ ├── hooks/ │ │ │ │ │ │ ├── useNovelPromotionWorkspaceController.ts │ │ │ │ │ │ ├── useRebuildConfirm.ts │ │ │ │ │ │ ├── useWorkspaceAssetLibraryShell.ts │ │ │ │ │ │ ├── useWorkspaceConfigActions.ts │ │ │ │ │ │ ├── useWorkspaceEpisodeStageData.ts │ │ │ │ │ │ ├── useWorkspaceExecution.ts │ │ │ │ │ │ ├── useWorkspaceModalEscape.ts │ │ │ │ │ │ ├── useWorkspaceProjectSnapshot.ts │ │ │ │ │ │ ├── useWorkspaceStageNavigation.ts │ │ │ │ │ │ ├── useWorkspaceStageRuntime.ts │ │ │ │ │ │ ├── useWorkspaceUserModels.ts │ │ │ │ │ │ ├── useWorkspaceVideoActions.ts │ │ │ │ │ │ └── workspace-controller-view-model.ts │ │ │ │ │ └── types.ts │ │ │ │ └── page.tsx │ │ │ ├── asset-hub/ │ │ │ │ ├── components/ │ │ │ │ │ ├── AddLocationModal.tsx │ │ │ │ │ ├── AssetGrid.tsx │ │ │ │ │ ├── CharacterCard.tsx │ │ │ │ │ ├── CharacterEditModal.tsx │ │ │ │ │ ├── FolderModal.tsx │ │ │ │ │ ├── FolderSidebar.tsx │ │ │ │ │ ├── LocationCard.tsx │ │ │ │ │ ├── LocationEditModal.tsx │ │ │ │ │ ├── VoiceCard.tsx │ │ │ │ │ ├── VoiceCreationModal.tsx │ │ │ │ │ ├── VoiceDesignDialog.tsx │ │ │ │ │ ├── VoicePickerDialog.tsx │ │ │ │ │ ├── VoiceSettings.tsx │ │ │ │ │ └── voice-creation/ │ │ │ │ │ ├── VoiceCreationForm.tsx │ │ │ │ │ ├── VoiceCreationModalLayout.tsx │ │ │ │ │ ├── VoiceCreationModalShell.tsx │ │ │ │ │ ├── VoicePreviewSection.tsx │ │ │ │ │ └── hooks/ │ │ │ │ │ └── useVoiceCreation.tsx │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ ├── api/ │ │ │ ├── admin/ │ │ │ │ └── download-logs/ │ │ │ │ └── route.ts │ │ │ ├── asset-hub/ │ │ │ │ ├── ai-design-character/ │ │ │ │ │ └── route.ts │ │ │ │ ├── ai-design-location/ │ │ │ │ │ └── route.ts │ │ │ │ ├── ai-modify-character/ │ │ │ │ │ └── route.ts │ │ │ │ ├── ai-modify-location/ │ │ │ │ │ └── route.ts │ │ │ │ ├── appearances/ │ │ │ │ │ └── route.ts │ │ │ │ ├── character-voice/ │ │ │ │ │ └── route.ts │ │ │ │ ├── characters/ │ │ │ │ │ ├── [characterId]/ │ │ │ │ │ │ ├── appearances/ │ │ │ │ │ │ │ └── [appearanceIndex]/ │ │ │ │ │ │ │ └── route.ts │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ ├── folders/ │ │ │ │ │ ├── [folderId]/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ ├── generate-image/ │ │ │ │ │ └── route.ts │ │ │ │ ├── locations/ │ │ │ │ │ ├── [locationId]/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ ├── modify-image/ │ │ │ │ │ └── route.ts │ │ │ │ ├── picker/ │ │ │ │ │ └── route.ts │ │ │ │ ├── reference-to-character/ │ │ │ │ │ └── route.ts │ │ │ │ ├── select-image/ │ │ │ │ │ └── route.ts │ │ │ │ ├── undo-image/ │ │ │ │ │ └── route.ts │ │ │ │ ├── update-asset-label/ │ │ │ │ │ └── route.ts │ │ │ │ ├── upload-image/ │ │ │ │ │ └── route.ts │ │ │ │ ├── upload-temp/ │ │ │ │ │ └── route.ts │ │ │ │ ├── voice-design/ │ │ │ │ │ └── route.ts │ │ │ │ └── voices/ │ │ │ │ ├── [id]/ │ │ │ │ │ └── route.ts │ │ │ │ ├── route.ts │ │ │ │ └── upload/ │ │ │ │ └── route.ts │ │ │ ├── auth/ │ │ │ │ ├── [...nextauth]/ │ │ │ │ │ └── route.ts │ │ │ │ └── register/ │ │ │ │ └── route.ts │ │ │ ├── cos/ │ │ │ │ └── image/ │ │ │ │ └── route.ts │ │ │ ├── files/ │ │ │ │ └── [...path]/ │ │ │ │ └── route.ts │ │ │ ├── novel-promotion/ │ │ │ │ └── [projectId]/ │ │ │ │ ├── ai-create-character/ │ │ │ │ │ └── route.ts │ │ │ │ ├── ai-create-location/ │ │ │ │ │ └── route.ts │ │ │ │ ├── ai-modify-appearance/ │ │ │ │ │ └── route.ts │ │ │ │ ├── ai-modify-location/ │ │ │ │ │ └── route.ts │ │ │ │ ├── ai-modify-shot-prompt/ │ │ │ │ │ └── route.ts │ │ │ │ ├── analyze/ │ │ │ │ │ └── route.ts │ │ │ │ ├── analyze-global/ │ │ │ │ │ └── route.ts │ │ │ │ ├── analyze-shot-variants/ │ │ │ │ │ └── route.ts │ │ │ │ ├── assets/ │ │ │ │ │ └── route.ts │ │ │ │ ├── character/ │ │ │ │ │ ├── appearance/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── confirm-selection/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ ├── character-profile/ │ │ │ │ │ ├── batch-confirm/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── confirm/ │ │ │ │ │ └── route.ts │ │ │ │ ├── character-voice/ │ │ │ │ │ └── route.ts │ │ │ │ ├── cleanup-unselected-images/ │ │ │ │ │ └── route.ts │ │ │ │ ├── clips/ │ │ │ │ │ ├── [clipId]/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ ├── copy-from-global/ │ │ │ │ │ └── route.ts │ │ │ │ ├── download-images/ │ │ │ │ │ └── route.ts │ │ │ │ ├── download-videos/ │ │ │ │ │ └── route.ts │ │ │ │ ├── download-voices/ │ │ │ │ │ └── route.ts │ │ │ │ ├── editor/ │ │ │ │ │ └── route.ts │ │ │ │ ├── episodes/ │ │ │ │ │ ├── [episodeId]/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── batch/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── route.ts │ │ │ │ │ ├── split/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── split-by-markers/ │ │ │ │ │ └── route.ts │ │ │ │ ├── generate-character-image/ │ │ │ │ │ └── route.ts │ │ │ │ ├── generate-image/ │ │ │ │ │ └── route.ts │ │ │ │ ├── generate-video/ │ │ │ │ │ └── route.ts │ │ │ │ ├── insert-panel/ │ │ │ │ │ └── route.ts │ │ │ │ ├── lip-sync/ │ │ │ │ │ └── route.ts │ │ │ │ ├── location/ │ │ │ │ │ ├── confirm-selection/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ ├── modify-asset-image/ │ │ │ │ │ └── route.ts │ │ │ │ ├── modify-storyboard-image/ │ │ │ │ │ └── route.ts │ │ │ │ ├── panel/ │ │ │ │ │ ├── route.ts │ │ │ │ │ └── select-candidate/ │ │ │ │ │ └── route.ts │ │ │ │ ├── panel-link/ │ │ │ │ │ └── route.ts │ │ │ │ ├── panel-variant/ │ │ │ │ │ └── route.ts │ │ │ │ ├── photography-plan/ │ │ │ │ │ └── route.ts │ │ │ │ ├── reference-to-character/ │ │ │ │ │ └── route.ts │ │ │ │ ├── regenerate-group/ │ │ │ │ │ └── route.ts │ │ │ │ ├── regenerate-panel-image/ │ │ │ │ │ └── route.ts │ │ │ │ ├── regenerate-single-image/ │ │ │ │ │ └── route.ts │ │ │ │ ├── regenerate-storyboard-text/ │ │ │ │ │ └── route.ts │ │ │ │ ├── route.ts │ │ │ │ ├── screenplay-conversion/ │ │ │ │ │ └── route.ts │ │ │ │ ├── script-to-storyboard-stream/ │ │ │ │ │ └── route.ts │ │ │ │ ├── select-character-image/ │ │ │ │ │ └── route.ts │ │ │ │ ├── select-location-image/ │ │ │ │ │ └── route.ts │ │ │ │ ├── speaker-voice/ │ │ │ │ │ └── route.ts │ │ │ │ ├── story-to-script-stream/ │ │ │ │ │ └── route.ts │ │ │ │ ├── storyboard-group/ │ │ │ │ │ └── route.ts │ │ │ │ ├── storyboards/ │ │ │ │ │ └── route.ts │ │ │ │ ├── undo-regenerate/ │ │ │ │ │ └── route.ts │ │ │ │ ├── update-appearance/ │ │ │ │ │ └── route.ts │ │ │ │ ├── update-asset-label/ │ │ │ │ │ └── route.ts │ │ │ │ ├── update-location/ │ │ │ │ │ └── route.ts │ │ │ │ ├── update-prompt/ │ │ │ │ │ └── route.ts │ │ │ │ ├── upload-asset-image/ │ │ │ │ │ └── route.ts │ │ │ │ ├── video-proxy/ │ │ │ │ │ └── route.ts │ │ │ │ ├── video-urls/ │ │ │ │ │ └── route.ts │ │ │ │ ├── voice-analyze/ │ │ │ │ │ └── route.ts │ │ │ │ ├── voice-design/ │ │ │ │ │ └── route.ts │ │ │ │ ├── voice-generate/ │ │ │ │ │ └── route.ts │ │ │ │ └── voice-lines/ │ │ │ │ └── route.ts │ │ │ ├── projects/ │ │ │ │ ├── [projectId]/ │ │ │ │ │ ├── assets/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── costs/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── data/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ ├── runs/ │ │ │ │ ├── [runId]/ │ │ │ │ │ ├── cancel/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── events/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── route.ts │ │ │ │ │ └── steps/ │ │ │ │ │ └── [stepKey]/ │ │ │ │ │ └── retry/ │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ ├── sse/ │ │ │ │ └── route.ts │ │ │ ├── storage/ │ │ │ │ └── sign/ │ │ │ │ └── route.ts │ │ │ ├── system/ │ │ │ │ └── boot-id/ │ │ │ │ └── route.ts │ │ │ ├── task-target-states/ │ │ │ │ └── route.ts │ │ │ ├── tasks/ │ │ │ │ ├── [taskId]/ │ │ │ │ │ └── route.ts │ │ │ │ ├── dismiss/ │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ ├── user/ │ │ │ │ ├── api-config/ │ │ │ │ │ ├── assistant/ │ │ │ │ │ │ ├── probe-media-template/ │ │ │ │ │ │ │ └── route.ts │ │ │ │ │ │ └── validate-media-template/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── probe-model-llm-protocol/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── route.ts │ │ │ │ │ ├── test-connection/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── test-provider/ │ │ │ │ │ └── route.ts │ │ │ │ ├── assistant/ │ │ │ │ │ └── chat/ │ │ │ │ │ └── route.ts │ │ │ │ ├── balance/ │ │ │ │ │ └── route.ts │ │ │ │ ├── costs/ │ │ │ │ │ ├── details/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ ├── models/ │ │ │ │ │ └── route.ts │ │ │ │ └── transactions/ │ │ │ │ └── route.ts │ │ │ └── user-preference/ │ │ │ └── route.ts │ │ ├── globals.css │ │ └── m/ │ │ └── [publicId]/ │ │ └── route.ts │ ├── components/ │ │ ├── ConfirmDialog.tsx │ │ ├── LanguageSwitcher.tsx │ │ ├── Navbar.tsx │ │ ├── ProgressToast.tsx │ │ ├── UpdateNoticeModal.tsx │ │ ├── ai-elements/ │ │ │ ├── conversation.tsx │ │ │ ├── message.tsx │ │ │ ├── reasoning.tsx │ │ │ └── tool.tsx │ │ ├── assistant/ │ │ │ ├── AssistantChatModal.tsx │ │ │ └── useAssistantChat.ts │ │ ├── auth/ │ │ │ └── PasswordStrengthIndicator.tsx │ │ ├── image-generation/ │ │ │ ├── ImageGenerationInlineCountButton.tsx │ │ │ └── ImageGenerationSlotOverlay.tsx │ │ ├── llm-console/ │ │ │ ├── LLMStageStreamCard.tsx │ │ │ └── index.ts │ │ ├── media/ │ │ │ ├── MediaImage.tsx │ │ │ └── MediaImageWithLoading.tsx │ │ ├── providers/ │ │ │ └── QueryProvider.tsx │ │ ├── shared/ │ │ │ └── assets/ │ │ │ ├── CharacterCreationModal.tsx │ │ │ ├── CharacterEditModal.tsx │ │ │ ├── GlobalAssetPicker.tsx │ │ │ ├── LocationCreationModal.tsx │ │ │ ├── LocationEditModal.tsx │ │ │ ├── character-creation/ │ │ │ │ ├── CharacterCreationForm.tsx │ │ │ │ ├── CharacterCreationPreview.tsx │ │ │ │ └── hooks/ │ │ │ │ └── useCharacterCreationSubmit.ts │ │ │ └── index.ts │ │ ├── task/ │ │ │ ├── TaskStatusInline.tsx │ │ │ └── TaskStatusOverlay.tsx │ │ ├── ui/ │ │ │ ├── CapsuleNav.tsx │ │ │ ├── ConfigModals.tsx │ │ │ ├── ImagePreviewModal.tsx │ │ │ ├── SegmentedControl.tsx │ │ │ ├── SharedComponents.tsx │ │ │ ├── ai-edit-style.ts │ │ │ ├── config-modals/ │ │ │ │ ├── ConfigConfirmModal.tsx │ │ │ │ ├── ConfigDeleteModal.tsx │ │ │ │ ├── ConfigEditModal.tsx │ │ │ │ ├── ModelCapabilityDropdown.tsx │ │ │ │ ├── WorldContextModal.tsx │ │ │ │ └── config-modal-selectors.tsx │ │ │ ├── icons/ │ │ │ │ ├── AISparklesIcon.tsx │ │ │ │ ├── AppIcon.tsx │ │ │ │ ├── RatioPreviewIcon.tsx │ │ │ │ ├── custom.tsx │ │ │ │ ├── index.ts │ │ │ │ └── registry.ts │ │ │ ├── model-dropdown-innovative.tsx │ │ │ ├── model-dropdown-ios.tsx │ │ │ ├── model-dropdown-variants.tsx │ │ │ ├── patterns/ │ │ │ │ ├── PanelCardV2.tsx │ │ │ │ ├── PanelEditFormV2.tsx │ │ │ │ ├── StoryboardHeaderV2.tsx │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── primitives/ │ │ │ │ ├── GlassButton.tsx │ │ │ │ ├── GlassChip.tsx │ │ │ │ ├── GlassField.tsx │ │ │ │ ├── GlassInput.tsx │ │ │ │ ├── GlassModalShell.tsx │ │ │ │ ├── GlassSurface.tsx │ │ │ │ ├── GlassTextarea.tsx │ │ │ │ └── index.ts │ │ │ └── select-variants.tsx │ │ └── voice/ │ │ ├── VoiceDesignDialogBase.tsx │ │ ├── VoiceDesignGeneratorSection.tsx │ │ └── voice-design-shared.ts │ ├── contexts/ │ │ └── ToastContext.tsx │ ├── features/ │ │ └── video-editor/ │ │ ├── components/ │ │ │ ├── Preview/ │ │ │ │ ├── RemotionPreview.tsx │ │ │ │ └── index.ts │ │ │ ├── Timeline/ │ │ │ │ ├── Timeline.tsx │ │ │ │ └── index.ts │ │ │ ├── TransitionPicker.tsx │ │ │ └── VideoEditorStage.tsx │ │ ├── hooks/ │ │ │ ├── useEditorActions.ts │ │ │ └── useEditorState.ts │ │ ├── index.ts │ │ ├── remotion/ │ │ │ ├── VideoComposition.tsx │ │ │ └── transitions/ │ │ │ └── index.tsx │ │ ├── types/ │ │ │ └── editor.types.ts │ │ └── utils/ │ │ ├── migration.ts │ │ └── time-utils.ts │ ├── hooks/ │ │ └── common/ │ │ ├── useCandidateSystem.ts │ │ └── useGithubReleaseUpdate.ts │ ├── i18n/ │ │ ├── navigation.ts │ │ └── routing.ts │ ├── i18n.ts │ ├── instrumentation.ts │ ├── lib/ │ │ ├── ai-runtime/ │ │ │ ├── client.ts │ │ │ ├── errors.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── api-auth.ts │ │ ├── api-config.ts │ │ ├── api-errors.ts │ │ ├── api-fetch.ts │ │ ├── app-meta.ts │ │ ├── ark-api.ts │ │ ├── ark-llm.ts │ │ ├── asset-utils/ │ │ │ ├── ai-design.ts │ │ │ └── index.ts │ │ ├── assets/ │ │ │ └── description-fields.ts │ │ ├── assistant-platform/ │ │ │ ├── errors.ts │ │ │ ├── index.ts │ │ │ ├── registry.ts │ │ │ ├── runtime.ts │ │ │ ├── skills/ │ │ │ │ ├── api-config-template.ts │ │ │ │ └── tutorial.ts │ │ │ ├── system-prompts.ts │ │ │ └── types.ts │ │ ├── async/ │ │ │ └── map-with-concurrency.ts │ │ ├── async-poll.ts │ │ ├── async-submit.ts │ │ ├── async-task-utils.ts │ │ ├── auth.ts │ │ ├── billing/ │ │ │ ├── cost.ts │ │ │ ├── currency.ts │ │ │ ├── errors.ts │ │ │ ├── index.ts │ │ │ ├── ledger.ts │ │ │ ├── mode.ts │ │ │ ├── money.ts │ │ │ ├── reporting.ts │ │ │ ├── runtime-usage.ts │ │ │ ├── service.ts │ │ │ ├── task-policy.ts │ │ │ └── types.ts │ │ ├── config-service.ts │ │ ├── constants.ts │ │ ├── contracts/ │ │ │ ├── image-urls-contract.test.ts │ │ │ └── image-urls-contract.ts │ │ ├── crypto-utils.ts │ │ ├── env.ts │ │ ├── episode-marker-detector.ts │ │ ├── error-handler.ts │ │ ├── error-utils.ts │ │ ├── errors/ │ │ │ ├── codes.ts │ │ │ ├── display.ts │ │ │ ├── extract.ts │ │ │ ├── normalize.ts │ │ │ ├── types.ts │ │ │ └── user-messages.ts │ │ ├── fonts.ts │ │ ├── gemini-batch-utils.ts │ │ ├── generator-api.ts │ │ ├── generators/ │ │ │ ├── ark.ts │ │ │ ├── audio/ │ │ │ │ ├── bailian.ts │ │ │ │ └── index.ts │ │ │ ├── base.ts │ │ │ ├── factory.ts │ │ │ ├── fal.ts │ │ │ ├── image/ │ │ │ │ ├── gemini-compatible.ts │ │ │ │ ├── google.ts │ │ │ │ ├── index.ts │ │ │ │ └── openai-compatible.ts │ │ │ ├── minimax.ts │ │ │ ├── official.ts │ │ │ ├── resolution-adapter.ts │ │ │ ├── video/ │ │ │ │ ├── google.ts │ │ │ │ ├── index.ts │ │ │ │ └── openai-compatible.ts │ │ │ └── vidu.ts │ │ ├── image-cache.ts │ │ ├── image-generation/ │ │ │ ├── count-preference.ts │ │ │ ├── count.ts │ │ │ ├── location-slots.ts │ │ │ ├── slot-state.ts │ │ │ └── use-image-generation-count.ts │ │ ├── image-label.ts │ │ ├── json-repair.ts │ │ ├── lipsync/ │ │ │ ├── index.ts │ │ │ ├── preprocess.ts │ │ │ ├── providers/ │ │ │ │ ├── bailian.ts │ │ │ │ ├── fal.ts │ │ │ │ └── vidu.ts │ │ │ └── types.ts │ │ ├── llm/ │ │ │ ├── chat-completion.ts │ │ │ ├── chat-stream.ts │ │ │ ├── completion-parts.ts │ │ │ ├── index.ts │ │ │ ├── providers/ │ │ │ │ ├── ark.ts │ │ │ │ ├── google.ts │ │ │ │ └── openai-compat.ts │ │ │ ├── reasoning-capability.ts │ │ │ ├── runtime-shared.ts │ │ │ ├── runtime.ts │ │ │ ├── stream-helpers.ts │ │ │ ├── stream-timeout.ts │ │ │ ├── types.ts │ │ │ ├── utils.ts │ │ │ └── vision.ts │ │ ├── llm-client.ts │ │ ├── llm-observe/ │ │ │ ├── config.ts │ │ │ ├── internal-stream-context.ts │ │ │ ├── internal-task.ts │ │ │ ├── route-task.ts │ │ │ ├── stage-pipeline.ts │ │ │ ├── task-policy.ts │ │ │ └── types.ts │ │ ├── logging/ │ │ │ ├── config.ts │ │ │ ├── context.ts │ │ │ ├── core.ts │ │ │ ├── file-writer.ts │ │ │ ├── redact.ts │ │ │ ├── semantic.ts │ │ │ └── types.ts │ │ ├── media/ │ │ │ ├── attach.ts │ │ │ ├── hash.ts │ │ │ ├── image-url.test.ts │ │ │ ├── image-url.ts │ │ │ ├── outbound-image.test.ts │ │ │ ├── outbound-image.ts │ │ │ ├── service.ts │ │ │ └── types.ts │ │ ├── media-process.ts │ │ ├── migrations/ │ │ │ └── gateway-route-openai-compat.ts │ │ ├── model-capabilities/ │ │ │ ├── catalog.ts │ │ │ ├── lookup.ts │ │ │ ├── video-effective.ts │ │ │ └── video-model-options.ts │ │ ├── model-config-contract.ts │ │ ├── model-gateway/ │ │ │ ├── index.ts │ │ │ ├── llm.ts │ │ │ ├── openai-compat/ │ │ │ │ ├── chat.ts │ │ │ │ ├── common.ts │ │ │ │ ├── image.ts │ │ │ │ ├── index.ts │ │ │ │ ├── responses.ts │ │ │ │ ├── template-image.ts │ │ │ │ ├── template-video.ts │ │ │ │ └── video.ts │ │ │ ├── router.ts │ │ │ └── types.ts │ │ ├── model-pricing/ │ │ │ ├── catalog.ts │ │ │ ├── lookup.ts │ │ │ ├── version.ts │ │ │ └── video-tier.ts │ │ ├── modes.ts │ │ ├── novel-promotion/ │ │ │ ├── insert-panel.ts │ │ │ ├── panel-ai-data-sync.ts │ │ │ ├── run-stream/ │ │ │ │ └── types.ts │ │ │ ├── script-to-storyboard/ │ │ │ │ └── orchestrator.ts │ │ │ ├── stages/ │ │ │ │ ├── contracts/ │ │ │ │ │ ├── video-stage-contract.ts │ │ │ │ │ └── voice-stage-contract.ts │ │ │ │ ├── video-stage-runtime/ │ │ │ │ │ ├── immediate-video-submission.ts │ │ │ │ │ ├── task-targets.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── useVideoDownloadAll.ts │ │ │ │ │ ├── useVideoFirstLastFrameFlow.ts │ │ │ │ │ ├── useVideoPanelLinking.ts │ │ │ │ │ ├── useVideoPanelViewport.ts │ │ │ │ │ ├── useVideoPanelsProjection.ts │ │ │ │ │ ├── useVideoPromptState.ts │ │ │ │ │ ├── useVideoStageUiState.ts │ │ │ │ │ ├── useVideoTaskStates.ts │ │ │ │ │ ├── useVideoVoiceLines.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── video-stage-runtime-core.tsx │ │ │ │ ├── voice-stage-runtime/ │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── useBindablePanelOptions.ts │ │ │ │ │ ├── useSpeakerAssetNavigation.ts │ │ │ │ │ ├── useVoiceGenerationActions.ts │ │ │ │ │ ├── useVoiceLineBindings.ts │ │ │ │ │ ├── useVoiceLineCrudActions.ts │ │ │ │ │ ├── useVoiceLineEditorState.ts │ │ │ │ │ ├── useVoicePlayback.ts │ │ │ │ │ ├── useVoiceRuntimeSync.ts │ │ │ │ │ ├── useVoiceSpeakerState.ts │ │ │ │ │ ├── useVoiceStageDataLoader.ts │ │ │ │ │ ├── useVoiceTaskState.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── voice-stage-runtime-core.tsx │ │ │ └── story-to-script/ │ │ │ ├── clip-matching.ts │ │ │ ├── orchestrator.ts │ │ │ └── types.ts │ │ ├── openai-compat-media-template.ts │ │ ├── openai-compat-template-runtime.ts │ │ ├── prisma-error.ts │ │ ├── prisma-retry.ts │ │ ├── prisma.ts │ │ ├── prompt-i18n/ │ │ │ ├── build-prompt.ts │ │ │ ├── catalog.ts │ │ │ ├── errors.ts │ │ │ ├── index.ts │ │ │ ├── prompt-ids.ts │ │ │ ├── template-store.ts │ │ │ └── types.ts │ │ ├── providers/ │ │ │ ├── bailian/ │ │ │ │ ├── audio.ts │ │ │ │ ├── catalog.ts │ │ │ │ ├── image.ts │ │ │ │ ├── index.ts │ │ │ │ ├── llm.ts │ │ │ │ ├── probe.ts │ │ │ │ ├── tts.ts │ │ │ │ ├── types.ts │ │ │ │ ├── video.ts │ │ │ │ ├── voice-cleanup.ts │ │ │ │ ├── voice-design.ts │ │ │ │ └── voice-manage.ts │ │ │ ├── fal/ │ │ │ │ └── base-url.ts │ │ │ ├── official/ │ │ │ │ └── model-registry.ts │ │ │ └── siliconflow/ │ │ │ ├── audio.ts │ │ │ ├── catalog.ts │ │ │ ├── image.ts │ │ │ ├── index.ts │ │ │ ├── llm.ts │ │ │ ├── probe.ts │ │ │ ├── types.ts │ │ │ └── video.ts │ │ ├── query/ │ │ │ ├── client.ts │ │ │ ├── hooks/ │ │ │ │ ├── index.ts │ │ │ │ ├── run-stream/ │ │ │ │ │ ├── event-parser.ts │ │ │ │ │ ├── recovered-run-subscription.ts │ │ │ │ │ ├── run-event-adapter.ts │ │ │ │ │ ├── run-request-executor.ts │ │ │ │ │ ├── run-stream-sse-body.ts │ │ │ │ │ ├── run-stream-state-runtime.ts │ │ │ │ │ ├── run-stream-view.ts │ │ │ │ │ ├── snapshot.ts │ │ │ │ │ ├── state-machine.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── useGlobalAssets.ts │ │ │ │ ├── useProjectAssets.ts │ │ │ │ ├── useProjectData.ts │ │ │ │ ├── useRunStreamState.ts │ │ │ │ ├── useSSE.ts │ │ │ │ ├── useScriptToStoryboardRunStream.ts │ │ │ │ ├── useStoryToScriptRunStream.ts │ │ │ │ ├── useStoryboards.ts │ │ │ │ ├── useTaskPresentation.ts │ │ │ │ ├── useTaskStatus.ts │ │ │ │ ├── useTaskTargetStateMap.ts │ │ │ │ ├── useUserModels.ts │ │ │ │ └── useVoiceLines.ts │ │ │ ├── keys.ts │ │ │ ├── mutations/ │ │ │ │ ├── asset-hub-character-mutations.ts │ │ │ │ ├── asset-hub-creation-mutations.ts │ │ │ │ ├── asset-hub-location-mutations.ts │ │ │ │ ├── asset-hub-mutations-runtime.ts │ │ │ │ ├── asset-hub-mutations-shared.ts │ │ │ │ ├── asset-hub-update-mutations.ts │ │ │ │ ├── asset-hub-voice-mutations.ts │ │ │ │ ├── character-base-mutations.ts │ │ │ │ ├── character-image-ops-mutations.ts │ │ │ │ ├── character-profile-mutations.ts │ │ │ │ ├── character-voice-mutations.ts │ │ │ │ ├── index.ts │ │ │ │ ├── location-image-mutations.ts │ │ │ │ ├── location-management-mutations.ts │ │ │ │ ├── mutation-shared.ts │ │ │ │ ├── storyboard-panel-mutations.ts │ │ │ │ ├── storyboard-prompt-mutations.ts │ │ │ │ ├── task-mutations.ts │ │ │ │ ├── useAssetHubMutations.ts │ │ │ │ ├── useCharacterMutations.core.ts │ │ │ │ ├── useCharacterMutations.ts │ │ │ │ ├── useEpisodeMutations.ts │ │ │ │ ├── useLocationMutations.core.ts │ │ │ │ ├── useLocationMutations.ts │ │ │ │ ├── useProjectConfigMutations.ts │ │ │ │ ├── useProjectMutations.ts │ │ │ │ ├── useStoryboardMutations.core.ts │ │ │ │ ├── useStoryboardMutations.ts │ │ │ │ ├── useVideoMutations.ts │ │ │ │ └── useVoiceMutations.ts │ │ │ └── task-target-overlay.ts │ │ ├── rate-limit.ts │ │ ├── redis.ts │ │ ├── run-runtime/ │ │ │ ├── publisher.ts │ │ │ ├── service.ts │ │ │ ├── task-bridge.ts │ │ │ ├── types.ts │ │ │ ├── workflow-lease.ts │ │ │ └── workflow.ts │ │ ├── server-boot.ts │ │ ├── srt.ts │ │ ├── sse/ │ │ │ └── shared-subscriber.ts │ │ ├── storage/ │ │ │ ├── bootstrap.ts │ │ │ ├── errors.ts │ │ │ ├── factory.ts │ │ │ ├── index.ts │ │ │ ├── init.ts │ │ │ ├── providers/ │ │ │ │ ├── cos.ts │ │ │ │ ├── local.ts │ │ │ │ └── minio.ts │ │ │ ├── signed-urls.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── storyboard-phases.ts │ │ ├── task/ │ │ │ ├── client.ts │ │ │ ├── error-message.ts │ │ │ ├── errors.ts │ │ │ ├── has-output.ts │ │ │ ├── intent.ts │ │ │ ├── presentation.ts │ │ │ ├── progress-message.ts │ │ │ ├── publisher.ts │ │ │ ├── queues.ts │ │ │ ├── reconcile.ts │ │ │ ├── resolve-locale.ts │ │ │ ├── service.ts │ │ │ ├── state-service.ts │ │ │ ├── submitter.ts │ │ │ ├── types.ts │ │ │ └── ui-payload.ts │ │ ├── update-check.ts │ │ ├── user-api/ │ │ │ ├── llm-test-connection.ts │ │ │ ├── model-llm-protocol-probe.ts │ │ │ ├── model-template/ │ │ │ │ ├── index.ts │ │ │ │ ├── probe.ts │ │ │ │ ├── save.ts │ │ │ │ ├── schema.ts │ │ │ │ └── validator.ts │ │ │ └── provider-test.ts │ │ ├── voice/ │ │ │ ├── generate-voice-line.ts │ │ │ └── provider-voice-binding.ts │ │ ├── word-count.ts │ │ ├── workers/ │ │ │ ├── handlers/ │ │ │ │ ├── analyze-global-parse.ts │ │ │ │ ├── analyze-global-persist.ts │ │ │ │ ├── analyze-global-prompt.ts │ │ │ │ ├── analyze-global.ts │ │ │ │ ├── analyze-novel.ts │ │ │ │ ├── asset-hub-ai-design.ts │ │ │ │ ├── asset-hub-ai-modify.ts │ │ │ │ ├── asset-hub-image-task-handler.ts │ │ │ │ ├── asset-hub-modify-task-handler.ts │ │ │ │ ├── character-image-task-handler.ts │ │ │ │ ├── character-profile-helpers.ts │ │ │ │ ├── character-profile.ts │ │ │ │ ├── clips-build.ts │ │ │ │ ├── episode-split.ts │ │ │ │ ├── image-task-handler-shared.ts │ │ │ │ ├── image-task-handlers-core.ts │ │ │ │ ├── image-task-handlers.ts │ │ │ │ ├── llm-proxy.ts │ │ │ │ ├── llm-stream.ts │ │ │ │ ├── location-image-task-handler.ts │ │ │ │ ├── modify-asset-image-task-handler.ts │ │ │ │ ├── modify-description-sync.ts │ │ │ │ ├── panel-image-task-handler.ts │ │ │ │ ├── panel-variant-task-handler.ts │ │ │ │ ├── reference-to-character-helpers.ts │ │ │ │ ├── reference-to-character.ts │ │ │ │ ├── resolve-analysis-model.ts │ │ │ │ ├── screenplay-convert-helpers.ts │ │ │ │ ├── screenplay-convert.ts │ │ │ │ ├── script-to-storyboard-atomic-retry.ts │ │ │ │ ├── script-to-storyboard-helpers.ts │ │ │ │ ├── script-to-storyboard.ts │ │ │ │ ├── shot-ai-persist.ts │ │ │ │ ├── shot-ai-prompt-appearance.ts │ │ │ │ ├── shot-ai-prompt-location.ts │ │ │ │ ├── shot-ai-prompt-runtime.ts │ │ │ │ ├── shot-ai-prompt-shot.ts │ │ │ │ ├── shot-ai-prompt-utils.ts │ │ │ │ ├── shot-ai-prompt.ts │ │ │ │ ├── shot-ai-tasks.ts │ │ │ │ ├── shot-ai-variants.ts │ │ │ │ ├── story-to-script-helpers.ts │ │ │ │ ├── story-to-script.ts │ │ │ │ ├── voice-analyze-helpers.ts │ │ │ │ ├── voice-analyze.ts │ │ │ │ └── voice-design.ts │ │ │ ├── image.worker.ts │ │ │ ├── index.ts │ │ │ ├── shared.ts │ │ │ ├── text.worker.ts │ │ │ ├── user-concurrency-gate.ts │ │ │ ├── utils.ts │ │ │ ├── video.worker.ts │ │ │ └── voice.worker.ts │ │ ├── workflow-concurrency.ts │ │ ├── workflow-engine/ │ │ │ └── dependencies.ts │ │ └── workspace/ │ │ └── model-setup.ts │ ├── middleware.ts │ ├── pages/ │ │ └── _document.tsx │ ├── styles/ │ │ ├── animations.css │ │ ├── ui-semantic-glass.css │ │ └── ui-tokens-glass.css │ └── types/ │ ├── character-profile.ts │ ├── next-auth.d.ts │ ├── project.ts │ └── storyboard-types.ts ├── standards/ │ ├── capabilities/ │ │ ├── catalog.example.json │ │ └── image-video.catalog.json │ ├── pricing/ │ │ └── image-video.pricing.json │ └── prompt-canary/ │ ├── screenplay_conversion.canary.json │ ├── story_to_script_clips.canary.json │ ├── storyboard_panels.canary.json │ └── voice_analysis.canary.json ├── tests/ │ ├── concurrency/ │ │ └── billing/ │ │ └── ledger.concurrency.test.ts │ ├── contracts/ │ │ ├── behavior-test-standard.md │ │ ├── requirements-matrix.test.ts │ │ ├── requirements-matrix.ts │ │ ├── route-behavior-matrix.ts │ │ ├── route-catalog.ts │ │ ├── task-type-catalog.ts │ │ └── tasktype-behavior-matrix.ts │ ├── fixtures/ │ │ └── billing/ │ │ └── cases.json │ ├── helpers/ │ │ ├── assertions.ts │ │ ├── auth.ts │ │ ├── billing-fixtures.ts │ │ ├── db-reset.ts │ │ ├── fakes/ │ │ │ ├── llm.ts │ │ │ ├── media.ts │ │ │ ├── providers.ts │ │ │ └── scenario-server.ts │ │ ├── fixtures.ts │ │ ├── mock-query-client.ts │ │ ├── prisma.ts │ │ └── request.ts │ ├── hidden/ │ │ └── README.md │ ├── integration/ │ │ ├── api/ │ │ │ ├── contract/ │ │ │ │ ├── crud-routes.test.ts │ │ │ │ ├── direct-submit-routes.test.ts │ │ │ │ ├── infra-routes.test.ts │ │ │ │ ├── llm-observe-routes.test.ts │ │ │ │ ├── run-cancel.route.test.ts │ │ │ │ ├── run-step-retry.route.test.ts │ │ │ │ └── task-infra-routes.test.ts │ │ │ ├── helpers/ │ │ │ │ └── call-route.ts │ │ │ └── specific/ │ │ │ ├── asset-hub-appearances-route.test.ts │ │ │ ├── asset-hub-generate-image-art-style.test.ts │ │ │ ├── asset-hub-location-create-no-auto-generate.test.ts │ │ │ ├── characters-post-reference-forwarding.test.ts │ │ │ ├── characters-post.test.ts │ │ │ ├── novel-promotion-character-style-forwarding.test.ts │ │ │ ├── novel-promotion-generate-image-art-style.test.ts │ │ │ ├── novel-promotion-location-style-forwarding.test.ts │ │ │ ├── novel-promotion-project-art-style-validation.test.ts │ │ │ ├── panel-variant-route.test.ts │ │ │ ├── project-create-default-audio-model.test.ts │ │ │ ├── reference-to-character-api.test.ts │ │ │ ├── speaker-voice-provider-contract.test.ts │ │ │ ├── user-api-config-probe-model-llm-protocol.test.ts │ │ │ ├── user-api-config-put.test.ts │ │ │ ├── user-assistant-chat-api-config.test.ts │ │ │ ├── user-models-audio-filter.test.ts │ │ │ ├── user-preference-art-style-validation.test.ts │ │ │ └── voice-generate-default-audio-model.test.ts │ │ ├── billing/ │ │ │ ├── api-contract.integration.test.ts │ │ │ ├── ledger.integration.test.ts │ │ │ ├── service.integration.test.ts │ │ │ ├── submitter.integration.test.ts │ │ │ └── worker-lifecycle.integration.test.ts │ │ ├── chain/ │ │ │ ├── image.chain.test.ts │ │ │ ├── text.chain.test.ts │ │ │ ├── video.chain.test.ts │ │ │ └── voice.chain.test.ts │ │ ├── provider/ │ │ │ ├── fal-provider.contract.test.ts │ │ │ └── openai-compat-provider.contract.test.ts │ │ ├── run-runtime/ │ │ │ └── retry-failed-step.integration.test.ts │ │ └── task/ │ │ └── create-task-dedupe.integration.test.ts │ ├── regression/ │ │ ├── panel-variant-cross-storyboard.test.ts │ │ ├── task-dedupe-recovery.test.ts │ │ └── task-enqueue-billing-rollback.test.ts │ ├── setup/ │ │ ├── env.ts │ │ ├── global-setup.ts │ │ └── global-teardown.ts │ ├── system/ │ │ ├── generate-image.system.test.ts │ │ ├── generate-video.system.test.ts │ │ ├── helpers/ │ │ │ ├── seed.ts │ │ │ ├── tasks.ts │ │ │ └── workers.ts │ │ ├── text-workflow.system.test.ts │ │ └── voice-generate.system.test.ts │ └── unit/ │ ├── ai-runtime/ │ │ └── errors.test.ts │ ├── api-config/ │ │ ├── assistant-chat-modal-content.test.ts │ │ ├── minimax-preset.test.ts │ │ ├── preset-coming-soon.test.ts │ │ ├── provider-card-assistant-saved-label.test.ts │ │ ├── provider-card-pricing-form.test.ts │ │ ├── provider-card-protocol-probe.test.ts │ │ ├── provider-card-shell.test.ts │ │ ├── provider-card-tutorial-modal.test.ts │ │ ├── use-api-config-filters.test.ts │ │ ├── use-assistant-chat-saved-events.test.ts │ │ └── use-providers-order.test.ts │ ├── assistant-platform/ │ │ ├── registry.test.ts │ │ ├── runtime.test.ts │ │ ├── skills-api-config-template.test.ts │ │ └── system-prompts.test.ts │ ├── async-poll-ocompat.test.ts │ ├── billing/ │ │ ├── cost-error-branches.test.ts │ │ ├── cost.test.ts │ │ ├── ledger-extra.test.ts │ │ ├── mode.test.ts │ │ ├── runtime-usage.test.ts │ │ ├── service.test.ts │ │ └── task-policy.test.ts │ ├── components/ │ │ ├── character-creation-modal.test.ts │ │ ├── image-generation-inline-count-button.test.ts │ │ ├── llm-stage-stream-card-error.test.ts │ │ ├── location-creation-modal.test.ts │ │ ├── navbar-download-logs.test.ts │ │ └── voice-design-shared.test.ts │ ├── generator-api-openai-template-required.test.ts │ ├── generator-api.test.ts │ ├── generators/ │ │ ├── factory.test.ts │ │ ├── fal-video-kling-presets.test.ts │ │ ├── image-provider-smoke.test.ts │ │ ├── openai-compatible-image.test.ts │ │ └── openai-compatible-video.test.ts │ ├── guards/ │ │ ├── api-route-contract-guard.test.ts │ │ ├── changed-file-test-impact-guard.test.ts │ │ ├── image-reference-normalization-guard.test.ts │ │ └── task-submit-compensation-guard.test.ts │ ├── helpers/ │ │ ├── api-fetch.test.ts │ │ ├── json-repair.test.ts │ │ ├── llm-stage-stream-card-output.test.ts │ │ ├── logging-core.test.ts │ │ ├── migrate-gateway-route-openai-compat.test.ts │ │ ├── prompt-suffix-regression.test.ts │ │ ├── recovered-run-subscription.test.ts │ │ ├── reference-to-character-helpers.test.ts │ │ ├── route-task-helpers.test.ts │ │ ├── run-request-executor.run-events.test.ts │ │ ├── run-stream-state-machine.test.ts │ │ ├── run-stream-view.test.ts │ │ ├── task-state-service.test.ts │ │ ├── task-submitter-helpers.test.ts │ │ ├── update-check.test.ts │ │ └── workspace-model-setup.test.ts │ ├── image-generation/ │ │ ├── count.test.ts │ │ └── slot-state.test.ts │ ├── lipsync-bailian.test.ts │ ├── lipsync-preprocess.test.ts │ ├── llm/ │ │ ├── ark-llm-thinking.test.ts │ │ ├── chat-completion-official-provider.test.ts │ │ ├── chat-completion-openai-compatible-protocol.test.ts │ │ ├── chat-stream-official-provider.test.ts │ │ ├── chat-stream-openai-compatible-protocol.test.ts │ │ ├── completion-parts-think-tag.test.ts │ │ └── reasoning-capability.test.ts │ ├── model-capabilities/ │ │ ├── bailian-video-capabilities.test.ts │ │ ├── image-resolution-default.test.ts │ │ └── video-effective.test.ts │ ├── model-gateway/ │ │ ├── llm.test.ts │ │ ├── openai-compat-responses.test.ts │ │ ├── openai-compat-template-image-output-urls.test.ts │ │ ├── openai-compat-template-renderer.test.ts │ │ ├── openai-compat-template-video-external-id.test.ts │ │ └── router.test.ts │ ├── novel-promotion/ │ │ ├── character-voice-mutations.test.ts │ │ ├── immediate-video-submission.test.ts │ │ ├── insert-panel-user-input.test.ts │ │ ├── panel-task-status-error-code.test.ts │ │ ├── use-tts-generation.test.ts │ │ ├── video-model-options.test.ts │ │ ├── video-panel-card-body.test.ts │ │ ├── video-panels-projection-error-code.test.ts │ │ ├── voice-generation-actions.test.ts │ │ ├── voice-runtime-sync.test.ts │ │ ├── voice-stage-data-loader.test.ts │ │ └── workspace-video-actions.test.ts │ ├── optimistic/ │ │ ├── ai-data-modal-state.test.ts │ │ ├── asset-hub-mutations.test.ts │ │ ├── panel-ai-data-sync.test.ts │ │ ├── panel-save-coordinator.test.ts │ │ ├── project-asset-mutations.test.ts │ │ ├── sse-invalidation.test.ts │ │ ├── task-target-overlay.test.ts │ │ └── task-target-state-map.test.ts │ ├── providers/ │ │ ├── bailian-llm.test.ts │ │ ├── bailian-tts.test.ts │ │ ├── bailian-video.test.ts │ │ ├── bailian-voice-cleanup.test.ts │ │ ├── bailian-voice-design.test.ts │ │ └── model-registry.test.ts │ ├── query/ │ │ └── project-location-generate-body.test.ts │ ├── run-runtime/ │ │ └── task-bridge.test.ts │ ├── storage/ │ │ ├── bootstrap.test.ts │ │ └── factory.test.ts │ ├── task/ │ │ ├── async-poll-bailian.test.ts │ │ ├── async-poll-external-id.test.ts │ │ ├── async-poll-openai.test.ts │ │ ├── error-catalog.contract.test.ts │ │ ├── error-message.test.ts │ │ ├── intent.test.ts │ │ ├── llm-observe-contract.test.ts │ │ ├── normalize-error.test.ts │ │ ├── presentation.test.ts │ │ ├── publisher.direct-run-events.test.ts │ │ └── publisher.replay.test.ts │ ├── user-api/ │ │ ├── llm-test-connection.test.ts │ │ ├── model-llm-protocol-probe.test.ts │ │ ├── model-template-save.test.ts │ │ ├── model-template-schema.test.ts │ │ ├── provider-test-compatible.test.ts │ │ └── provider-test.test.ts │ ├── voice/ │ │ ├── generate-voice-line.test.ts │ │ └── provider-voice-binding.test.ts │ ├── worker/ │ │ ├── analyze-global.test.ts │ │ ├── analyze-novel.test.ts │ │ ├── asset-hub-ai-design.test.ts │ │ ├── asset-hub-ai-modify.test.ts │ │ ├── asset-hub-image-suffix.test.ts │ │ ├── character-image-task-handler.test.ts │ │ ├── character-profile.test.ts │ │ ├── clips-build.test.ts │ │ ├── episode-split.test.ts │ │ ├── image-task-handlers-core.test.ts │ │ ├── image-worker.test.ts │ │ ├── llm-proxy.test.ts │ │ ├── llm-stream.test.ts │ │ ├── location-image-task-handler.test.ts │ │ ├── modify-image-reference-description.test.ts │ │ ├── panel-image-task-handler.test.ts │ │ ├── panel-variant-task-handler.test.ts │ │ ├── reference-to-character.test.ts │ │ ├── resolve-analysis-model.test.ts │ │ ├── screenplay-convert.test.ts │ │ ├── script-to-storyboard-atomic-retry.test.ts │ │ ├── script-to-storyboard-orchestrator.retry.test.ts │ │ ├── script-to-storyboard.test.ts │ │ ├── shared.direct-run-events.test.ts │ │ ├── shot-ai-prompt-appearance.test.ts │ │ ├── shot-ai-prompt-location.test.ts │ │ ├── shot-ai-prompt-shot.test.ts │ │ ├── shot-ai-tasks.test.ts │ │ ├── shot-ai-variants.test.ts │ │ ├── story-to-script-orchestrator.retry.test.ts │ │ ├── story-to-script.test.ts │ │ ├── user-concurrency-gate.test.ts │ │ ├── video-generation-resume.test.ts │ │ ├── video-worker.test.ts │ │ ├── voice-analyze.test.ts │ │ ├── voice-design.test.ts │ │ ├── voice-line-parse-helpers.test.ts │ │ └── voice-worker.test.ts │ └── workspace/ │ ├── episode-selection.test.ts │ └── rebuild-confirm.test.ts ├── tsconfig.json ├── vitest.config.ts └── vitest.core-coverage.config.ts
Showing preview only (534K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (5296 symbols across 977 files)
FILE: .tmp_check_task.ts
function main (line 5) | async function main() {
FILE: extract_chinese.py
function extract_chinese_strings (line 10) | def extract_chinese_strings(file_path):
function scan_directory (line 57) | def scan_directory(base_path,exclude_patterns=['test-ui']):
FILE: lib/prompts/proxy.ts
function setProxy (line 2) | async function setProxy() {
FILE: scripts/billing-cleanup-pending-freezes.ts
type CleanupStats (line 4) | type CleanupStats = {
function hasApplyFlag (line 12) | function hasApplyFlag() {
function parseHoursArg (line 16) | function parseHoursArg(defaultHours: number) {
function writeJson (line 24) | function writeJson(payload: unknown) {
function writeError (line 28) | function writeError(payload: unknown) {
function main (line 32) | async function main() {
FILE: scripts/billing-reconcile-ledger.ts
type UserLedgerRow (line 4) | type UserLedgerRow = {
function hasStrictFlag (line 13) | function hasStrictFlag() {
function write (line 17) | function write(payload: unknown) {
function main (line 21) | async function main() {
FILE: scripts/bull-board.ts
function unauthorized (line 17) | function unauthorized(res: Response) {
function basicAuthMiddleware (line 22) | function basicAuthMiddleware(req: Request, res: Response, next: NextFunc...
function shutdown (line 91) | async function shutdown(signal: string) {
FILE: scripts/check-api-handler.ts
constant ALLOWLIST (line 4) | const ALLOWLIST = new Set([
function main (line 10) | function main() {
FILE: scripts/check-capability-catalog.mjs
constant CATALOG_DIR (line 4) | const CATALOG_DIR = path.resolve(process.cwd(), 'standards/capabilities')
constant CAPABILITY_NAMESPACES (line 5) | const CAPABILITY_NAMESPACES = new Set(['llm', 'image', 'video', 'audio',...
constant CAPABILITY_NAMESPACE_ALLOWED_FIELDS (line 6) | const CAPABILITY_NAMESPACE_ALLOWED_FIELDS = {
constant CAPABILITY_NAMESPACE_I18N_FIELDS (line 22) | const CAPABILITY_NAMESPACE_I18N_FIELDS = {
constant MODEL_TYPES (line 35) | const MODEL_TYPES = new Set(['llm', 'image', 'video', 'audio', 'lipsync'])
function isRecord (line 37) | function isRecord(value) {
function isNonEmptyString (line 41) | function isNonEmptyString(value) {
function isI18nKey (line 45) | function isI18nKey(value) {
function isStringArray (line 49) | function isStringArray(value) {
function isNumberArray (line 53) | function isNumberArray(value) {
function isBooleanArray (line 57) | function isBooleanArray(value) {
function parseModelKeyStrict (line 61) | function parseModelKeyStrict(value) {
function pushIssue (line 72) | function pushIssue(issues, file, index, field, message) {
function validateAllowedFields (line 76) | function validateAllowedFields(issues, file, index, namespace, namespace...
function validateFieldI18nMap (line 89) | function validateFieldI18nMap(issues, file, index, namespace, namespaceV...
function validateCapabilitiesForModelType (line 152) | function validateCapabilitiesForModelType(issues, file, index, modelType...
function listCatalogFiles (line 263) | async function listCatalogFiles() {
function readCatalog (line 270) | async function readCatalog(filePath) {
function main (line 279) | async function main() {
FILE: scripts/check-image-urls-contract.ts
type AppearanceRow (line 5) | type AppearanceRow = {
type DynamicModel (line 11) | type DynamicModel = {
constant BATCH_SIZE (line 15) | const BATCH_SIZE = 500
constant MODELS (line 17) | const MODELS: Array<{ name: string; model: string }> = [
function print (line 24) | function print(message: string) {
function checkModel (line 28) | async function checkModel(modelName: string, modelKey: string) {
function main (line 91) | async function main() {
FILE: scripts/check-log-semantic.ts
type Rule (line 3) | type Rule = {
constant RULES (line 8) | const RULES: Rule[] = [
function read (line 39) | function read(file: string) {
function checkRules (line 43) | function checkRules() {
function checkSubmitTaskRoutes (line 56) | function checkSubmitTaskRoutes() {
function walk (line 76) | function walk(dir: string): string[] {
function main (line 92) | function main() {
FILE: scripts/check-media-normalization.ts
constant TARGETS (line 3) | const TARGETS = ['src/app/api', 'src/lib']
constant EXTRACT_ALLOWLIST (line 5) | const EXTRACT_ALLOWLIST = new Set<string>([
constant FETCH_MEDIA_ALLOWLIST (line 10) | const FETCH_MEDIA_ALLOWLIST = new Set<string>([
function run (line 23) | function run(cmd: string): string {
function parseLines (line 35) | function parseLines(output: string): string[] {
function getFile (line 42) | function getFile(line: string): string {
function getCode (line 46) | function getCode(line: string): string {
function extractFetchArg (line 51) | function extractFetchArg(code: string): string {
function isSafeFetchArg (line 56) | function isSafeFetchArg(arg: string): boolean {
function isMediaLikeFetchArg (line 64) | function isMediaLikeFetchArg(arg: string): boolean {
function main (line 68) | function main() {
FILE: scripts/check-model-config-contract.mjs
constant STRICT (line 3) | const STRICT = process.argv.includes('--strict')
constant MODEL_FIELDS (line 4) | const MODEL_FIELDS = [
constant MAX_SAMPLES (line 12) | const MAX_SAMPLES = 200
constant CAPABILITY_NAMESPACES (line 13) | const CAPABILITY_NAMESPACES = new Set(['llm', 'image', 'video', 'audio',...
constant MODEL_TYPES (line 14) | const MODEL_TYPES = new Set(['llm', 'image', 'video', 'audio', 'lipsync'])
constant CAPABILITY_NAMESPACE_ALLOWED_FIELDS (line 15) | const CAPABILITY_NAMESPACE_ALLOWED_FIELDS = {
constant CAPABILITY_NAMESPACE_I18N_FIELDS (line 30) | const CAPABILITY_NAMESPACE_I18N_FIELDS = {
function isRecord (line 51) | function isRecord(value) {
function isNonEmptyString (line 55) | function isNonEmptyString(value) {
function isStringArray (line 59) | function isStringArray(value) {
function isNumberArray (line 63) | function isNumberArray(value) {
function parseModelKeyStrict (line 67) | function parseModelKeyStrict(value) {
function addSample (line 82) | function addSample(summary, sample) {
function pushIssue (line 87) | function pushIssue(issues, field, message) {
function isI18nKey (line 91) | function isI18nKey(value) {
function validateAllowedFields (line 95) | function validateAllowedFields(issues, namespace, namespaceValue) {
function validateFieldI18nMap (line 108) | function validateFieldI18nMap(issues, namespace, namespaceValue) {
function validateCapabilities (line 185) | function validateCapabilities(modelType, capabilities) {
function main (line 290) | async function main() {
FILE: scripts/check-no-console.ts
constant ALLOWLIST (line 3) | const ALLOWLIST = new Set<string>([
function run (line 17) | function run(cmd: string): string {
function main (line 29) | function main() {
FILE: scripts/check-outbound-image-runtime-sample.ts
type AnyJson (line 4) | type AnyJson = unknown
type Match (line 6) | type Match = {
type Options (line 11) | type Options = {
type FailureType (line 21) | type FailureType = 'normalize' | 'model' | 'cancelled' | 'other'
constant MODEL_ERROR_CODES (line 23) | const MODEL_ERROR_CODES = new Set([
function parseNumberArg (line 31) | function parseNumberArg(name: string, fallback: number): number {
function parseStringArg (line 38) | function parseStringArg(name: string): string | null {
function parseBooleanArg (line 45) | function parseBooleanArg(name: string, fallback = false): boolean {
function parseOptions (line 52) | function parseOptions(): Options {
function toExcerpt (line 64) | function toExcerpt(value: string, max = 180): string {
function findStringMatches (line 69) | function findStringMatches(
function classifyFailure (line 93) | function classifyFailure(task: {
function main (line 131) | async function main() {
FILE: scripts/check-outbound-image-success-rate.ts
type StatusCount (line 4) | type StatusCount = Record<string, number>
type WindowSummary (line 6) | type WindowSummary = {
type Options (line 16) | type Options = {
constant DEFAULT_MINUTES (line 27) | const DEFAULT_MINUTES = 60 * 24 * 7
constant DEFAULT_TOLERANCE_PCT (line 28) | const DEFAULT_TOLERANCE_PCT = 2
constant DEFAULT_MIN_FINISHED_SAMPLES (line 29) | const DEFAULT_MIN_FINISHED_SAMPLES = 20
function parseNumberArg (line 31) | function parseNumberArg(name: string, fallback: number): number {
function parseBooleanArg (line 38) | function parseBooleanArg(name: string, fallback = false): boolean {
function parseStringArg (line 45) | function parseStringArg(name: string): string | null {
function parseOptions (line 52) | function parseOptions(): Options {
function asPct (line 68) | function asPct(value: number | null): string {
function getSuccessRate (line 72) | function getSuccessRate(completed: number, failed: number): number | null {
function summarizeRows (line 78) | function summarizeRows(
function fetchWindowSummary (line 103) | async function fetchWindowSummary(params: {
function main (line 132) | async function main() {
FILE: scripts/check-outbound-image-unification.ts
type Rule (line 4) | type Rule = {
function readFile (line 10) | function readFile(relativePath: string): string {
function main (line 131) | function main() {
FILE: scripts/check-pricing-catalog.mjs
constant CATALOG_DIR (line 4) | const CATALOG_DIR = path.resolve(process.cwd(), 'standards/pricing')
constant CAPABILITY_CATALOG_FILE (line 5) | const CAPABILITY_CATALOG_FILE = path.resolve(process.cwd(), 'standards/c...
constant API_TYPES (line 6) | const API_TYPES = new Set(['text', 'image', 'video', 'voice', 'voice-des...
constant PRICING_MODES (line 7) | const PRICING_MODES = new Set(['flat', 'capability'])
constant TEXT_TOKEN_TYPES (line 8) | const TEXT_TOKEN_TYPES = new Set(['input', 'output'])
function isRecord (line 10) | function isRecord(value) {
function isNonEmptyString (line 14) | function isNonEmptyString(value) {
function isCapabilityValue (line 18) | function isCapabilityValue(value) {
function isFiniteNumber (line 22) | function isFiniteNumber(value) {
function pushIssue (line 26) | function pushIssue(issues, file, index, field, message) {
function getProviderKey (line 30) | function getProviderKey(providerId) {
function buildModelKey (line 35) | function buildModelKey(modelType, provider, modelId) {
function listCatalogFiles (line 39) | async function listCatalogFiles() {
function readCatalog (line 46) | async function readCatalog(filePath) {
function readCapabilityCatalog (line 55) | async function readCapabilityCatalog() {
function extractCapabilityOptionFields (line 64) | function extractCapabilityOptionFields(modelType, capabilities) {
function buildCapabilityOptionFieldMap (line 79) | function buildCapabilityOptionFieldMap(capabilityEntries) {
function validateTier (line 99) | function validateTier(issues, file, index, tier, tierIndex) {
function validateTextCapabilityTiers (line 126) | function validateTextCapabilityTiers(issues, file, index, tiers) {
function validateMediaCapabilityTierFields (line 159) | function validateMediaCapabilityTierFields(issues, file, index, item, ti...
function validateDuplicateCapabilityTiers (line 189) | function validateDuplicateCapabilityTiers(issues, file, index, tiers) {
function validatePricing (line 203) | function validatePricing(issues, file, index, item, capabilityOptionFiel...
function main (line 243) | async function main() {
FILE: scripts/cleanup-remove-legacy-voice-data.ts
type CharacterVoiceRecord (line 3) | type CharacterVoiceRecord = {
type SpeakerVoiceConfig (line 8) | type SpeakerVoiceConfig = {
type CleanupSummary (line 15) | type CleanupSummary = {
function hasPlayableAudioUrl (line 23) | function hasPlayableAudioUrl(value: unknown) {
function normalizeVoiceType (line 27) | function normalizeVoiceType(customVoiceUrl: string | null) {
function cleanupCharacterTable (line 31) | async function cleanupCharacterTable(records: CharacterVoiceRecord[], ta...
function normalizeSpeakerVoices (line 57) | function normalizeSpeakerVoices(payload: string): {
function main (line 119) | async function main() {
FILE: scripts/diagnose-project.ts
function diagnoseProject (line 10) | async function diagnoseProject(projectId: string) {
FILE: scripts/guards/api-route-contract-guard.mjs
constant API_HANDLER_ALLOWLIST (line 11) | const API_HANDLER_ALLOWLIST = new Set([
constant PUBLIC_ROUTE_ALLOWLIST (line 17) | const PUBLIC_ROUTE_ALLOWLIST = new Set([
constant AUTH_CALL_PATTERNS (line 26) | const AUTH_CALL_PATTERNS = [
function fail (line 32) | function fail(title, details = []) {
function walk (line 40) | function walk(dir, out = []) {
function toRel (line 55) | function toRel(fullPath) {
function hasApiHandlerWrapper (line 59) | function hasApiHandlerWrapper(content) {
function hasRequiredAuth (line 63) | function hasRequiredAuth(content) {
function inspectRouteContract (line 67) | function inspectRouteContract(relPath, content) {
function findApiRouteContractViolations (line 81) | function findApiRouteContractViolations(scanRoot = root) {
function main (line 92) | function main() {
FILE: scripts/guards/changed-file-test-impact-guard.mjs
constant RULES (line 6) | const RULES = [
function normalizeChangedFiles (line 39) | function normalizeChangedFiles(rawFiles) {
function readGitChangedFiles (line 46) | function readGitChangedFiles() {
function inspectChangedFiles (line 59) | function inspectChangedFiles(changedFiles) {
function fail (line 76) | function fail(violations) {
function runCli (line 84) | function runCli() {
FILE: scripts/guards/file-line-count-guard.mjs
constant ROOT (line 5) | const ROOT = process.cwd()
constant RULES (line 7) | const RULES = [
FILE: scripts/guards/image-reference-normalization-guard.mjs
constant NORMALIZATION_HELPER_ALLOWLIST (line 11) | const NORMALIZATION_HELPER_ALLOWLIST = new Set([
constant ACCEPTED_NORMALIZATION_MARKERS (line 15) | const ACCEPTED_NORMALIZATION_MARKERS = [
function fail (line 21) | function fail(title, details = []) {
function walk (line 29) | function walk(dir, out = []) {
function toRel (line 44) | function toRel(fullPath) {
function usesGenerationReferenceImages (line 48) | function usesGenerationReferenceImages(content) {
function hasNormalizationMarker (line 52) | function hasNormalizationMarker(content) {
function inspectImageReferenceNormalization (line 56) | function inspectImageReferenceNormalization(relPath, content) {
function findImageReferenceNormalizationViolations (line 65) | function findImageReferenceNormalizationViolations(scanRoot = root) {
function main (line 76) | function main() {
FILE: scripts/guards/locale-navigation-guard.mjs
function toRel (line 19) | function toRel(fullPath) {
function walk (line 23) | function walk(dir, out = []) {
function gatherTargetFiles (line 40) | function gatherTargetFiles() {
function findViolations (line 51) | function findViolations(content, relPath) {
FILE: scripts/guards/no-api-direct-llm-call.mjs
function fail (line 12) | function fail(title, details = []) {
function toRel (line 20) | function toRel(fullPath) {
function walk (line 24) | function walk(dir, out = []) {
function isAllowedFile (line 42) | function isAllowedFile(relPath) {
function collectViolations (line 46) | function collectViolations(fullPath) {
FILE: scripts/guards/no-duplicate-endpoint-entry.mjs
constant ROOT (line 5) | const ROOT = process.cwd()
constant API_ROOT (line 6) | const API_ROOT = path.join(ROOT, 'src', 'app', 'api')
constant KNOWN_DUPLICATE_GROUPS (line 8) | const KNOWN_DUPLICATE_GROUPS = [
FILE: scripts/guards/no-hardcoded-model-capabilities.mjs
function fail (line 21) | function fail(title, details = []) {
function toRel (line 29) | function toRel(fullPath) {
function walk (line 33) | function walk(dir, out = []) {
FILE: scripts/guards/no-internal-task-sync-fallback.mjs
function fail (line 12) | function fail(title, details = []) {
function toRel (line 20) | function toRel(fullPath) {
function walk (line 24) | function walk(dir, out = []) {
function isAllowedFile (line 41) | function isAllowedFile(relPath) {
function collectViolations (line 45) | function collectViolations(fullPath) {
FILE: scripts/guards/no-media-provider-bypass.mjs
function fail (line 14) | function fail(title, details = []) {
function toRel (line 22) | function toRel(fullPath) {
function walk (line 26) | function walk(dir, out = []) {
FILE: scripts/guards/no-model-key-downgrade.mjs
function fail (line 19) | function fail(title, details = []) {
function toRel (line 27) | function toRel(fullPath) {
function walk (line 31) | function walk(dir, out = []) {
function collectViolations (line 48) | function collectViolations(filePath) {
function assertFileContains (line 69) | function assertFileContains(relativePath, requiredSnippets) {
FILE: scripts/guards/no-multiple-sources-of-truth.mjs
function fail (line 35) | function fail(title, details = []) {
function toRel (line 43) | function toRel(fullPath) {
function walk (line 47) | function walk(dir, out = []) {
function collectLineViolations (line 62) | function collectLineViolations(fullPath) {
function collectFileViolations (line 80) | function collectFileViolations(fullPath) {
FILE: scripts/guards/no-provider-guessing.mjs
function fail (line 12) | function fail(title, details = []) {
function toRel (line 20) | function toRel(fullPath) {
function walk (line 24) | function walk(dir, out = []) {
FILE: scripts/guards/no-server-mirror-state.mjs
function fail (line 28) | function fail(title, details = []) {
function toRel (line 36) | function toRel(fullPath) {
function walk (line 40) | function walk(dir, out = []) {
function collectViolations (line 56) | function collectViolations(fullPath) {
FILE: scripts/guards/prompt-ab-regression.mjs
function fail (line 13) | function fail(title, details = []) {
function parseCatalog (line 21) | function parseCatalog(text) {
function extractPlaceholders (line 33) | function extractPlaceholders(template) {
function replaceAll (line 44) | function replaceAll(template, variables) {
function setDiff (line 53) | function setDiff(left, right) {
FILE: scripts/guards/prompt-i18n-guard.mjs
function fail (line 22) | function fail(title, details = []) {
function toRel (line 30) | function toRel(fullPath) {
function walk (line 34) | function walk(dir, out = []) {
function listSourceFiles (line 49) | function listSourceFiles() {
function collectDirectPromptReadViolations (line 55) | function collectDirectPromptReadViolations() {
function collectLanguageDirectiveViolations (line 77) | function collectLanguageDirectiveViolations() {
function collectLegacyPromptFiles (line 108) | function collectLegacyPromptFiles() {
function verifyPromptCatalogCoverage (line 114) | function verifyPromptCatalogCoverage() {
FILE: scripts/guards/prompt-json-canary-guard.mjs
constant CANARY_FILES (line 9) | const CANARY_FILES = {
constant TEMPLATE_TOKEN_REQUIREMENTS (line 16) | const TEMPLATE_TOKEN_REQUIREMENTS = {
function fail (line 70) | function fail(title, details = []) {
function isRecord (line 78) | function isRecord(value) {
function isString (line 82) | function isString(value) {
function isNumber (line 86) | function isNumber(value) {
function readJson (line 90) | function readJson(relativePath) {
function validateClipCanary (line 102) | function validateClipCanary(value) {
function validateScreenplayCanary (line 118) | function validateScreenplayCanary(value) {
function validateStoryboardPanelsCanary (line 164) | function validateStoryboardPanelsCanary(value) {
function validateVoiceAnalysisCanary (line 191) | function validateVoiceAnalysisCanary(value) {
function checkTemplateTokens (line 209) | function checkTemplateTokens(pathStem, requiredTokens) {
FILE: scripts/guards/prompt-semantic-regression.mjs
function fail (line 26) | function fail(title, details = []) {
function parseCatalog (line 34) | function parseCatalog(text) {
function extractPlaceholders (line 46) | function extractPlaceholders(template) {
FILE: scripts/guards/task-loading-guard.mjs
function walkFiles (line 10) | function walkFiles(dir, out = []) {
function toPosixRelative (line 24) | function toPosixRelative(filePath) {
function collectMatches (line 28) | function collectMatches(files, pattern) {
function fail (line 44) | function fail(title, lines) {
FILE: scripts/guards/task-submit-compensation-guard.mjs
constant CREATE_PATTERN (line 10) | const CREATE_PATTERN = /\.\s*create\s*\(/
constant SUBMIT_TASK_PATTERN (line 11) | const SUBMIT_TASK_PATTERN = /\bsubmitTask\s*\(/
constant ROLLBACK_PATTERN (line 12) | const ROLLBACK_PATTERN = /rollback|compensat/i
function fail (line 14) | function fail(title, details = []) {
function walk (line 22) | function walk(dir, out = []) {
function toRel (line 37) | function toRel(fullPath) {
function inspectTaskSubmitCompensation (line 41) | function inspectTaskSubmitCompensation(relPath, content) {
function findTaskSubmitCompensationViolations (line 50) | function findTaskSubmitCompensationViolations(scanRoot = root) {
function main (line 61) | function main() {
FILE: scripts/guards/task-target-states-no-polling-guard.mjs
function fail (line 9) | function fail(title, details = []) {
function readFile (line 17) | function readFile(relativePath) {
function walk (line 25) | function walk(dir, out = []) {
function toRel (line 39) | function toRel(fullPath) {
function collectPattern (line 43) | function collectPattern(pattern) {
FILE: scripts/guards/test-behavior-quality-guard.mjs
function fail (line 15) | function fail(title, details = []) {
function walk (line 23) | function walk(dir, out = []) {
function toRel (line 38) | function toRel(fullPath) {
FILE: scripts/guards/test-behavior-route-coverage-guard.mjs
function fail (line 10) | function fail(title, details = []) {
FILE: scripts/guards/test-behavior-tasktype-coverage-guard.mjs
function fail (line 10) | function fail(title, details = []) {
FILE: scripts/guards/test-route-coverage-guard.mjs
function fail (line 10) | function fail(title, details = []) {
function walk (line 18) | function walk(dir, out = []) {
function toRel (line 33) | function toRel(fullPath) {
FILE: scripts/guards/test-tasktype-coverage-guard.mjs
function fail (line 10) | function fail(title, details = []) {
FILE: scripts/media-archive-legacy-refs.ts
constant BACKUP_ROOT (line 8) | const BACKUP_ROOT = path.join(process.cwd(), 'data', 'migration-backups')
constant BATCH_SIZE (line 9) | const BATCH_SIZE = 500
type DynamicModel (line 10) | type DynamicModel = {
function nowStamp (line 16) | function nowStamp() {
function checksum (line 20) | function checksum(value: string) {
function toSelect (line 24) | function toSelect(fields: string[]) {
function main (line 30) | async function main() {
FILE: scripts/media-backfill-refs.ts
constant BATCH_SIZE (line 6) | const BATCH_SIZE = 200
type DynamicModel (line 7) | type DynamicModel = {
function toSelect (line 13) | function toSelect(fields: string[]) {
function backfillModel (line 19) | async function backfillModel(mapping: (typeof MEDIA_MODEL_MAPPINGS)[numb...
function main (line 90) | async function main() {
FILE: scripts/media-build-unreferenced-index.ts
type StorageEntry (line 9) | type StorageEntry = {
type CosBucketPage (line 14) | type CosBucketPage = {
type DynamicModel (line 19) | type DynamicModel = {
constant BACKUP_ROOT (line 24) | const BACKUP_ROOT = path.join(process.cwd(), 'data', 'migration-backups')
function nowStamp (line 26) | function nowStamp() {
function listLocalObjects (line 30) | async function listLocalObjects(): Promise<StorageEntry[]> {
function listCosObjects (line 62) | async function listCosObjects(): Promise<StorageEntry[]> {
function listStorageObjects (line 108) | async function listStorageObjects() {
function buildReferencedKeySet (line 116) | async function buildReferencedKeySet() {
function main (line 169) | async function main() {
FILE: scripts/media-mapping.ts
type MediaFieldMapping (line 1) | type MediaFieldMapping = {
type MediaModelMapping (line 6) | type MediaModelMapping = {
constant MEDIA_MODEL_MAPPINGS (line 12) | const MEDIA_MODEL_MAPPINGS: MediaModelMapping[] = [
FILE: scripts/media-restore-dry-run.ts
constant BACKUP_ROOT (line 6) | const BACKUP_ROOT = path.join(process.cwd(), 'data', 'migration-backups')
type CountMap (line 8) | type CountMap = Record<string, number>
function findLatestBackupDir (line 10) | async function findLatestBackupDir() {
function readExpectedCounts (line 32) | async function readExpectedCounts(backupDir: string): Promise<CountMap> {
function currentCounts (line 39) | async function currentCounts(): Promise<CountMap> {
function printDiff (line 69) | function printDiff(expected: CountMap, actual: CountMap) {
function main (line 85) | async function main() {
FILE: scripts/media-safety-backup.ts
type SnapshotTask (line 8) | type SnapshotTask = {
type StorageIndexRow (line 13) | type StorageIndexRow = {
type CosBucketPage (line 20) | type CosBucketPage = {
constant BACKUP_ROOT (line 31) | const BACKUP_ROOT = path.join(process.cwd(), 'data', 'migration-backups')
function nowStamp (line 33) | function nowStamp() {
function toJson (line 37) | function toJson(value: unknown) {
function writeJson (line 45) | async function writeJson(filePath: string, data: unknown) {
function sha256Text (line 49) | function sha256Text(input: string) {
function resolveDatabaseFilePath (line 53) | function resolveDatabaseFilePath(databaseUrl: string | undefined): strin...
function listLocalFilesRecursively (line 63) | async function listLocalFilesRecursively(rootDir: string, prefix = ''): ...
function listCosObjects (line 90) | async function listCosObjects(): Promise<StorageIndexRow[]> {
function buildStorageIndex (line 136) | async function buildStorageIndex(): Promise<{ storageType: string; rows:...
function snapshotTables (line 153) | async function snapshotTables(backupDir: string) {
function writeChecksums (line 179) | async function writeChecksums(backupDir: string) {
function backupDbFile (line 194) | async function backupDbFile(backupDir: string) {
function main (line 207) | async function main() {
FILE: scripts/migrate-cancelled-to-failed.ts
constant OLD_STATUS (line 3) | const OLD_STATUS = 'cancelled'
constant NEW_STATUS (line 4) | const NEW_STATUS = 'failed'
constant OLD_EVENT_TYPE (line 5) | const OLD_EVENT_TYPE = 'task.cancelled'
constant NEW_EVENT_TYPE (line 6) | const NEW_EVENT_TYPE = 'task.failed'
constant MIGRATION_ERROR_CODE (line 7) | const MIGRATION_ERROR_CODE = 'USER_CANCELLED'
constant MIGRATION_ERROR_MESSAGE (line 8) | const MIGRATION_ERROR_MESSAGE = '用户已停止任务。'
function log (line 10) | function log(message: string) {
function logError (line 14) | function logError(message: string) {
function main (line 18) | async function main() {
FILE: scripts/migrate-image-urls-contract.ts
type AppearanceRow (line 5) | type AppearanceRow = {
type DynamicModel (line 11) | type DynamicModel = {
type FieldName (line 16) | type FieldName = 'imageUrls' | 'previousImageUrls'
type NormalizeResult (line 18) | type NormalizeResult = {
type ModelStats (line 24) | type ModelStats = {
constant BATCH_SIZE (line 31) | const BATCH_SIZE = 200
constant APPLY (line 32) | const APPLY = process.argv.includes('--apply')
constant MODELS (line 34) | const MODELS: Array<{ name: string; model: string }> = [
function print (line 41) | function print(message: string) {
function normalizeField (line 45) | function normalizeField(raw: string | null): NormalizeResult {
function migrateModel (line 97) | async function migrateModel(modelName: string, modelKey: string) {
function main (line 205) | async function main() {
FILE: scripts/migrate-local-to-minio.ts
constant LOCAL_DIR (line 15) | const LOCAL_DIR = process.env.LOCAL_UPLOAD_DIR || './data/uploads'
constant MINIO_ENDPOINT (line 16) | const MINIO_ENDPOINT = process.env.MINIO_ENDPOINT || 'http://127.0.0.1:1...
constant MINIO_BUCKET (line 17) | const MINIO_BUCKET = process.env.MINIO_BUCKET || 'waoowaoo'
constant MINIO_REGION (line 18) | const MINIO_REGION = process.env.MINIO_REGION || 'us-east-1'
constant MINIO_ACCESS_KEY (line 19) | const MINIO_ACCESS_KEY = process.env.MINIO_ACCESS_KEY || 'minioadmin'
constant MINIO_SECRET_KEY (line 20) | const MINIO_SECRET_KEY = process.env.MINIO_SECRET_KEY || 'minioadmin'
constant CONCURRENCY (line 21) | const CONCURRENCY = parseInt(process.env.MIGRATE_CONCURRENCY || '10')
constant DRY_RUN (line 22) | const DRY_RUN = process.env.MIGRATE_DRY_RUN === 'true'
function guessContentType (line 36) | function guessContentType(filename: string): string {
function formatBytes (line 55) | function formatBytes(bytes: number): string {
function scanLocalFiles (line 64) | async function scanLocalFiles(dir: string, basePath = ''): Promise<Array...
function objectExists (line 94) | async function objectExists(key: string): Promise<boolean> {
function uploadFile (line 104) | async function uploadFile(file: { localPath: string; key: string; size: ...
function runBatched (line 131) | async function runBatched<T>(items: T[], concurrency: number, fn: (item:...
function main (line 139) | async function main() {
FILE: scripts/migrate-to-minio.ts
constant CONFIG (line 20) | const CONFIG = {
constant LOG_LEVELS (line 47) | const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }
function log (line 48) | function log(level: string, message: string, ...args: unknown[]) {
function scanLocalFiles (line 66) | async function scanLocalFiles(dir: string, basePath = ''): Promise<Array...
function calculateHash (line 97) | async function calculateHash(filePath: string): Promise<string> {
function loadProgress (line 109) | async function loadProgress(): Promise<Set<string>> {
function saveProgress (line 122) | async function saveProgress(migratedKeys: Set<string>) {
function ensureBucket (line 131) | async function ensureBucket() {
function uploadFile (line 157) | async function uploadFile(fileInfo: {localPath: string, key: string, siz...
function guessContentType (line 198) | function guessContentType(filename: string): string {
function formatBytes (line 217) | function formatBytes(bytes: number): string {
function runWithConcurrency (line 226) | async function runWithConcurrency<T>(tasks: Array<() => Promise<T>>, con...
function main (line 247) | async function main() {
FILE: scripts/migrations/migrate-capability-selections.ts
constant APPLY (line 9) | const APPLY = process.argv.includes('--apply')
constant USER_IMAGE_MODEL_FIELDS (line 11) | const USER_IMAGE_MODEL_FIELDS = [
constant PROJECT_IMAGE_MODEL_FIELDS (line 18) | const PROJECT_IMAGE_MODEL_FIELDS = [
type UserImageModelField (line 25) | type UserImageModelField = typeof USER_IMAGE_MODEL_FIELDS[number]
type ProjectImageModelField (line 26) | type ProjectImageModelField = typeof PROJECT_IMAGE_MODEL_FIELDS[number]
type UserPreferenceRow (line 28) | interface UserPreferenceRow {
type ProjectRow (line 39) | interface ProjectRow {
type MigrationSummary (line 52) | interface MigrationSummary {
function isRecord (line 67) | function isRecord(value: unknown): value is Record<string, unknown> {
function isCapabilityValue (line 71) | function isCapabilityValue(value: unknown): value is CapabilityValue {
function normalizeSelections (line 75) | function normalizeSelections(raw: unknown): CapabilitySelections {
function parseSelections (line 95) | function parseSelections(raw: string | null): CapabilitySelections {
function serializeSelections (line 104) | function serializeSelections(selections: CapabilitySelections): string |...
function getCapabilityResolutionOptions (line 109) | function getCapabilityResolutionOptions(
function ensureModelResolutionSelection (line 126) | function ensureModelResolutionSelection(input: {
function collectModelKeys (line 148) | function collectModelKeys<RowType>(
function migrateUserPreferences (line 163) | async function migrateUserPreferences(summary: MigrationSummary) {
function migrateProjects (line 210) | async function migrateProjects(summary: MigrationSummary) {
function main (line 271) | async function main() {
FILE: scripts/migrations/migrate-custom-pricing-v2.ts
constant APPLY (line 3) | const APPLY = process.argv.includes('--apply')
type PreferenceRow (line 5) | type PreferenceRow = {
type MigrationSummary (line 11) | type MigrationSummary = {
function isRecord (line 19) | function isRecord(value: unknown): value is Record<string, unknown> {
function parseCustomModels (line 23) | function parseCustomModels(raw: string | null): unknown[] | null {
function migrateLegacyCustomPricing (line 34) | function migrateLegacyCustomPricing(raw: unknown): {
function migrateCustomModel (line 73) | function migrateCustomModel(rawModel: unknown): { changed: boolean; next...
function main (line 92) | async function main() {
FILE: scripts/migrations/migrate-gateway-route-openai-compat.ts
constant APPLY (line 4) | const APPLY = process.argv.includes('--apply')
type PreferenceRow (line 6) | type PreferenceRow = {
type MigrationSummary (line 12) | type MigrationSummary = {
function main (line 23) | async function main() {
FILE: scripts/migrations/migrate-graph-artifacts-unique-index.ts
constant APPLY (line 3) | const APPLY = process.argv.includes('--apply')
constant REQUIRED_INDEX_NAME (line 4) | const REQUIRED_INDEX_NAME = 'graph_artifacts_runId_stepKey_artifactType_...
constant REQUIRED_COLUMNS (line 5) | const REQUIRED_COLUMNS = ['runId', 'stepKey', 'artifactType', 'refId'] a...
type IndexRow (line 7) | type IndexRow = {
type DuplicateRow (line 14) | type DuplicateRow = {
type MigrationSummary (line 22) | type MigrationSummary = {
function parseIntSafe (line 37) | function parseIntSafe(value: number | string) {
function hasRequiredUniqueIndex (line 42) | function hasRequiredUniqueIndex(rows: IndexRow[]) {
function toNumber (line 70) | function toNumber(value: bigint | number) {
function loadIndexRows (line 75) | async function loadIndexRows() {
function loadDuplicateGroups (line 79) | async function loadDuplicateGroups() {
function main (line 90) | async function main() {
FILE: scripts/migrations/migrate-model-config-contract.ts
type ModelField (line 12) | type ModelField =
type PreferenceRow (line 20) | type PreferenceRow = {
type ProjectRow (line 32) | type ProjectRow = {
type MigrationIssue (line 46) | type MigrationIssue = {
type MigrationReport (line 65) | type MigrationReport = {
type NormalizedModel (line 82) | type NormalizedModel = {
constant APPLY (line 93) | const APPLY = process.argv.includes('--apply')
constant MAX_ISSUES (line 94) | const MAX_ISSUES = 500
constant MODEL_FIELDS (line 95) | const MODEL_FIELDS: readonly ModelField[] = [
constant LEGACY_MODEL_ID_MAP (line 104) | const LEGACY_MODEL_ID_MAP = new Map<string, string>([
function parseReportPathArg (line 122) | function parseReportPathArg(): string {
function isRecord (line 133) | function isRecord(value: unknown): value is Record<string, unknown> {
function toTrimmedString (line 137) | function toTrimmedString(value: unknown): string {
function isUnifiedModelType (line 141) | function isUnifiedModelType(value: unknown): value is UnifiedModelType {
function stableStringify (line 149) | function stableStringify(value: unknown): string {
function parseCustomModels (line 153) | function parseCustomModels(raw: string | null): { ok: true; value: unkno...
function normalizeModel (line 164) | function normalizeModel(
function addIssue (line 263) | function addIssue(report: MigrationReport, issue: MigrationIssue) {
function normalizeModelFieldValue (line 268) | function normalizeModelFieldValue(
function main (line 315) | async function main() {
FILE: scripts/migrations/migrate-qwen-to-bailian.ts
constant APPLY (line 4) | const APPLY = process.argv.includes('--apply')
type PreferenceRow (line 6) | type PreferenceRow = {
type StoredProvider (line 21) | type StoredProvider = {
type StoredModel (line 30) | type StoredModel = {
type MigrationConflict (line 39) | type MigrationConflict = {
type MigrationSummary (line 44) | type MigrationSummary = {
type DefaultModelField (line 56) | type DefaultModelField =
constant DEFAULT_MODEL_FIELDS (line 65) | const DEFAULT_MODEL_FIELDS: readonly DefaultModelField[] = [
function isRecord (line 75) | function isRecord(value: unknown): value is Record<string, unknown> {
function readTrimmedString (line 79) | function readTrimmedString(value: unknown): string {
function parseProviders (line 83) | function parseProviders(raw: string | null): StoredProvider[] | null {
function parseModels (line 107) | function parseModels(raw: string | null): StoredModel[] | null {
function parseCapabilityDefaults (line 139) | function parseCapabilityDefaults(raw: string | null): CapabilitySelectio...
function migrateProviderId (line 161) | function migrateProviderId(providerId: string): string {
function migrateModelKey (line 172) | function migrateModelKey(rawModelKey: string): string {
function migrateDefaultModel (line 179) | function migrateDefaultModel(rawValue: string | null): string | null {
function hasProviderByKey (line 186) | function hasProviderByKey(providers: StoredProvider[], providerKey: stri...
function main (line 194) | async function main() {
FILE: scripts/migrations/migrate-release-blockers.ts
type Mode (line 4) | type Mode = 'dry-run' | 'apply'
type UserPreferenceRow (line 6) | type UserPreferenceRow = {
type NovelProjectRow (line 22) | type NovelProjectRow = {
type StoredProvider (line 34) | type StoredProvider = {
type StoredModel (line 43) | type StoredModel = {
type ParseResult (line 50) | type ParseResult<T> = {
type MigrationSummary (line 55) | type MigrationSummary = {
type MysqlIndexRow (line 96) | type MysqlIndexRow = {
type DuplicateGroupRow (line 103) | type DuplicateGroupRow = {
type CountRow (line 111) | type CountRow = {
type DefaultModelField (line 115) | type DefaultModelField =
type ProjectModelField (line 125) | type ProjectModelField =
type UserPreferenceUpdateData (line 133) | type UserPreferenceUpdateData = Partial<Record<DefaultModelField, string...
type NovelProjectUpdateData (line 139) | type NovelProjectUpdateData = Partial<Record<ProjectModelField, string |...
constant MODE (line 143) | const MODE: Mode = process.argv.includes('--dry-run') ? 'dry-run' : 'apply'
constant APPLY (line 144) | const APPLY = MODE === 'apply'
constant OFFICIAL_ONLY_PROVIDER_KEYS (line 146) | const OFFICIAL_ONLY_PROVIDER_KEYS = new Set(['bailian', 'siliconflow'])
constant DEFAULT_MODEL_FIELDS (line 147) | const DEFAULT_MODEL_FIELDS: readonly DefaultModelField[] = [
constant PROJECT_MODEL_FIELDS (line 157) | const PROJECT_MODEL_FIELDS: readonly ProjectModelField[] = [
constant REQUIRED_GRAPH_ARTIFACT_UNIQUE_COLUMNS (line 165) | const REQUIRED_GRAPH_ARTIFACT_UNIQUE_COLUMNS = ['runId', 'stepKey', 'art...
function isRecord (line 167) | function isRecord(value: unknown): value is Record<string, unknown> {
function readTrimmedString (line 171) | function readTrimmedString(value: unknown): string {
function toNullableModelField (line 175) | function toNullableModelField(raw: string | null | undefined): string | ...
function getProviderKey (line 180) | function getProviderKey(providerId: string): string {
function migrateProviderId (line 185) | function migrateProviderId(providerId: string): string {
function migrateModelKey (line 195) | function migrateModelKey(rawModelKey: string): string {
function providerPriorityByOriginalKey (line 203) | function providerPriorityByOriginalKey(originalProviderId: string): numb...
function normalizeGatewayRoute (line 210) | function normalizeGatewayRoute(
function parseJsonArray (line 221) | function parseJsonArray(raw: string | null): ParseResult<unknown[]> {
function parseJsonRecord (line 232) | function parseJsonRecord(raw: string | null): ParseResult<Record<string,...
function migrateProviders (line 243) | function migrateProviders(
function migrateModels (line 336) | function migrateModels(
function migrateModelField (line 433) | function migrateModelField(
function migrateCapabilitySelections (line 471) | function migrateCapabilitySelections(
function toIndexNumber (line 546) | function toIndexNumber(value: number | string): number {
function hasRequiredGraphArtifactUniqueIndex (line 551) | function hasRequiredGraphArtifactUniqueIndex(rows: MysqlIndexRow[]): boo...
function toNumber (line 578) | function toNumber(value: bigint | number): number {
function loadGraphArtifactIndexes (line 583) | async function loadGraphArtifactIndexes(): Promise<MysqlIndexRow[]> {
function countGraphArtifactDuplicateGroups (line 587) | async function countGraphArtifactDuplicateGroups(): Promise<number> {
function sampleGraphArtifactDuplicateGroups (line 601) | async function sampleGraphArtifactDuplicateGroups(limit: number): Promis...
function dedupeGraphArtifacts (line 612) | async function dedupeGraphArtifacts(): Promise<number> {
function addGraphArtifactUniqueIndex (line 628) | async function addGraphArtifactUniqueIndex(): Promise<void> {
function migrateUserPreferences (line 634) | async function migrateUserPreferences(summary: MigrationSummary): Promis...
function migrateNovelProjects (line 719) | async function migrateNovelProjects(summary: MigrationSummary): Promise<...
function migrateGraphArtifacts (line 776) | async function migrateGraphArtifacts(summary: MigrationSummary): Promise...
function main (line 817) | async function main() {
FILE: scripts/task-error-stats.ts
function parseMinutesArg (line 4) | function parseMinutesArg() {
function main (line 10) | async function main() {
FILE: scripts/test-full-image-flow.ts
function testFullImageFlow (line 13) | async function testFullImageFlow() {
FILE: scripts/test-image-url-flow.ts
function testImageUrlFlow (line 14) | async function testImageUrlFlow() {
FILE: scripts/test-minio.ts
function testMinio (line 11) | async function testMinio() {
FILE: scripts/test-sign-api.ts
function testSignApi (line 12) | async function testSignApi() {
FILE: scripts/watchdog.ts
constant INTERVAL_MS (line 10) | const INTERVAL_MS = Number.parseInt(process.env.WATCHDOG_INTERVAL_MS || ...
constant HEARTBEAT_TIMEOUT_MS (line 11) | const HEARTBEAT_TIMEOUT_MS = Number.parseInt(process.env.TASK_HEARTBEAT_...
constant TASK_TYPE_SET (line 12) | const TASK_TYPE_SET: ReadonlySet<string> = new Set(Object.values(TASK_TY...
constant LOG_CLEANUP_INTERVAL_TICKS (line 14) | const LOG_CLEANUP_INTERVAL_TICKS = Math.ceil(3600_000 / INTERVAL_MS)
function toTaskType (line 21) | function toTaskType(value: string): TaskType | null {
function toTaskPayload (line 28) | function toTaskPayload(value: unknown): Record<string, unknown> | null {
function recoverQueuedTasks (line 35) | async function recoverQueuedTasks() {
function cleanupZombieProcessingTasks (line 128) | async function cleanupZombieProcessingTasks() {
function tick (line 187) | async function tick() {
FILE: src/app/[locale]/auth/signin/page.tsx
function SignIn (line 9) | function SignIn() {
FILE: src/app/[locale]/auth/signup/page.tsx
function SignUp (line 10) | function SignUp() {
FILE: src/app/[locale]/layout.tsx
type SupportedLocale (line 24) | type SupportedLocale = (typeof locales)[number]
function generateMetadata (line 27) | async function generateMetadata({ params }: { params: Promise<{ locale: ...
function generateStaticParams (line 42) | function generateStaticParams() {
function LocaleLayout (line 46) | async function LocaleLayout({
FILE: src/app/[locale]/page.tsx
function Home (line 11) | function Home() {
FILE: src/app/[locale]/profile/components/ApiConfigTab.tsx
function ApiConfigTab (line 5) | function ApiConfigTab() {
FILE: src/app/[locale]/profile/components/api-config-tab/ApiConfigProviderList.tsx
type DefaultModels (line 25) | interface DefaultModels {
type ApiConfigProviderListProps (line 36) | interface ApiConfigProviderListProps {
function ApiConfigProviderList (line 66) | function ApiConfigProviderList({
type SortableProviderCardItemProps (line 228) | interface SortableProviderCardItemProps {
function SortableProviderCardItem (line 234) | function SortableProviderCardItem({ providerId, dragLabel, children }: S...
FILE: src/app/[locale]/profile/components/api-config-tab/ApiConfigTabContainer.tsx
type TestStepStatus (line 21) | type TestStepStatus = 'pass' | 'fail' | 'skip'
type TestStep (line 22) | interface TestStep {
type TestStatus (line 29) | type TestStatus = 'idle' | 'testing' | 'passed' | 'failed'
type CustomProviderType (line 31) | type CustomProviderType = 'gemini-compatible' | 'openai-compatible'
function isRecord (line 59) | function isRecord(value: unknown): value is Record<string, unknown> {
function isCapabilityValue (line 63) | function isCapabilityValue(value: unknown): value is CapabilityValue {
function extractCapabilityFieldsFromModel (line 67) | function extractCapabilityFieldsFromModel(
function parseBySample (line 82) | function parseBySample(input: string, sample: CapabilityValue): Capabili...
function toCapabilityFieldLabel (line 88) | function toCapabilityFieldLabel(field: string): string {
function ApiConfigTabContainer (line 92) | function ApiConfigTabContainer() {
FILE: src/app/[locale]/profile/components/api-config-tab/ApiConfigToolbar.tsx
type ApiConfigToolbarProps (line 7) | interface ApiConfigToolbarProps {
function ApiConfigToolbar (line 16) | function ApiConfigToolbar({
FILE: src/app/[locale]/profile/components/api-config-tab/DefaultModelCards.tsx
type ModelType (line 10) | type ModelType = 'llm' | 'image' | 'video' | 'audio' | 'lipsync' | 'voic...
type ModelOption (line 12) | interface ModelOption {
type DefaultModelField (line 20) | type DefaultModelField =
type DefaultModelCardsProps (line 31) | interface DefaultModelCardsProps {
function isRecord (line 64) | function isRecord(value: unknown): value is Record<string, unknown> {
function isCapabilityValue (line 67) | function isCapabilityValue(value: unknown): value is CapabilityValue {
function resolveModel (line 71) | function resolveModel(
function computeCapabilityFields (line 87) | function computeCapabilityFields(current: ModelOption | null, modelType:...
function SmartSelector (line 102) | function SmartSelector({
function DefaultModelCards (line 193) | function DefaultModelCards(allProps: DefaultModelCardsProps) {
FILE: src/app/[locale]/profile/components/api-config-tab/hooks/useApiConfigFilters.ts
type UseApiConfigFiltersParams (line 7) | interface UseApiConfigFiltersParams {
type EnabledModelOption (line 12) | interface EnabledModelOption extends CustomModel {
constant DYNAMIC_PROVIDER_PREFIXES (line 16) | const DYNAMIC_PROVIDER_PREFIXES = ['gemini-compatible', 'openai-compatib...
constant ALWAYS_SHOW_PROVIDERS (line 17) | const ALWAYS_SHOW_PROVIDERS: string[] = []
constant HIDDEN_PROVIDER_KEYS (line 19) | const HIDDEN_PROVIDER_KEYS = new Set(['siliconflow'])
constant PROVIDER_MODEL_TYPES (line 20) | const PROVIDER_MODEL_TYPES: Array<'llm' | 'image' | 'video' | 'audio' | ...
constant DEFAULT_AUDIO_EXCLUDED_MODEL_IDS (line 21) | const DEFAULT_AUDIO_EXCLUDED_MODEL_IDS = new Set([
constant MODEL_PROVIDER_KEYS (line 24) | const MODEL_PROVIDER_KEYS = [
function isProviderModelType (line 36) | function isProviderModelType(type: CustomModel['type']): type is 'llm' |...
function isDefaultModelType (line 40) | function isDefaultModelType(type: CustomModel['type']): type is 'llm' | ...
function isAudioDefaultCandidate (line 44) | function isAudioDefaultCandidate(model: CustomModel): boolean {
function hasProviderApiKey (line 49) | function hasProviderApiKey(provider: Provider | undefined): boolean {
function useApiConfigFilters (line 56) | function useApiConfigFilters({
FILE: src/app/[locale]/profile/components/api-config/DefaultModelSection.tsx
type DefaultModelSectionProps (line 8) | interface DefaultModelSectionProps {
function DefaultModelSection (line 20) | function DefaultModelSection({
FILE: src/app/[locale]/profile/components/api-config/ProviderCard.tsx
function ProviderCard (line 10) | function ProviderCard({
FILE: src/app/[locale]/profile/components/api-config/ProviderSection.tsx
type ProviderSectionProps (line 8) | interface ProviderSectionProps {
function ProviderSection (line 21) | function ProviderSection({
FILE: src/app/[locale]/profile/components/api-config/hooks.ts
type DefaultModels (line 27) | interface DefaultModels {
type WorkflowConcurrency (line 39) | interface WorkflowConcurrency {
type UseProvidersReturn (line 45) | interface UseProvidersReturn {
function mergeProvidersForDisplay (line 72) | function mergeProvidersForDisplay(
function isRecord (line 123) | function isRecord(value: unknown): value is Record<string, unknown> {
function composePricingDisplayKey (line 127) | function composePricingDisplayKey(type: CustomModel['type'], provider: s...
function parsePricingDisplayMap (line 131) | function parsePricingDisplayMap(raw: unknown): PricingDisplayMap {
constant DEFAULT_WORKFLOW_CONCURRENCY (line 154) | const DEFAULT_WORKFLOW_CONCURRENCY: WorkflowConcurrency = {
function parseWorkflowConcurrency (line 160) | function parseWorkflowConcurrency(raw: unknown): WorkflowConcurrency {
constant PRICING_DISPLAY_ALIASES (line 181) | const PRICING_DISPLAY_ALIASES: Readonly<Record<string, string>> = {
function resolvePricingDisplay (line 185) | function resolvePricingDisplay(
function applyPricingDisplay (line 209) | function applyPricingDisplay(model: CustomModel, map: PricingDisplayMap)...
function useProviders (line 238) | function useProviders(): UseProvidersReturn {
FILE: src/app/[locale]/profile/components/api-config/provider-card/ModelTemplateAssistantModal.tsx
type ModelTemplateAssistantModalProps (line 10) | interface ModelTemplateAssistantModalProps {
function ModelTemplateAssistantModal (line 15) | function ModelTemplateAssistantModal({ t, state }: ModelTemplateAssistan...
FILE: src/app/[locale]/profile/components/api-config/provider-card/ProviderAdvancedFields.tsx
type ProviderAdvancedFieldsProps (line 14) | interface ProviderAdvancedFieldsProps {
constant MODEL_TYPES (line 63) | const MODEL_TYPES: readonly ProviderCardModelType[] = ['llm', 'image', '...
function getAddableModelTypesForProvider (line 65) | function getAddableModelTypesForProvider(providerId: string): ProviderCa...
function shouldShowOpenAICompatVideoHint (line 71) | function shouldShowOpenAICompatVideoHint(
function shouldShowDefaultTabs (line 78) | function shouldShowDefaultTabs(providerId: string): boolean {
function getVisibleModelTypesForProvider (line 83) | function getVisibleModelTypesForProvider(
function formatPriceAmount (line 98) | function formatPriceAmount(amount: number): string {
function getModelPriceTexts (line 104) | function getModelPriceTexts(model: CustomModel, t: ProviderCardTranslato...
function ProviderAdvancedFields (line 128) | function ProviderAdvancedFields({
type ModelRowProps (line 346) | interface ModelRowProps {
function ModelRow (line 356) | function ModelRow({
FILE: src/app/[locale]/profile/components/api-config/provider-card/ProviderBaseFields.tsx
type ProviderBaseFieldsProps (line 7) | interface ProviderBaseFieldsProps {
function ProviderBaseFields (line 13) | function ProviderBaseFields({ provider, t, state }: ProviderBaseFieldsPr...
FILE: src/app/[locale]/profile/components/api-config/provider-card/ProviderCardShell.tsx
type ProviderCardShellProps (line 11) | interface ProviderCardShellProps {
function getCompatibilityLayerBadgeLabel (line 23) | function getCompatibilityLayerBadgeLabel(
function StatusIcon (line 34) | function StatusIcon({ connected }: { connected: boolean }) {
function ProviderCardShell (line 43) | function ProviderCardShell({
FILE: src/app/[locale]/profile/components/api-config/provider-card/hooks/useProviderCardState.ts
type KeyTestStepStatus (line 29) | type KeyTestStepStatus = 'pass' | 'fail' | 'skip'
type KeyTestStep (line 30) | interface KeyTestStep {
type KeyTestStatus (line 37) | type KeyTestStatus = 'idle' | 'testing' | 'passed' | 'failed'
type UseProviderCardStateParams (line 41) | interface UseProviderCardStateParams {
constant EMPTY_MODEL_FORM (line 54) | const EMPTY_MODEL_FORM: ModelFormState = {
type AddModelCustomPricing (line 68) | type AddModelCustomPricing = {
type BuildCustomPricingResult (line 74) | type BuildCustomPricingResult =
type ProviderConnectionPayload (line 78) | interface ProviderConnectionPayload {
type LlmProtocolType (line 85) | type LlmProtocolType = 'responses' | 'chat-completions'
type ProbeModelLlmProtocolSuccessResponse (line 87) | type ProbeModelLlmProtocolSuccessResponse = {
type ProbeModelLlmProtocolFailureResponse (line 93) | type ProbeModelLlmProtocolFailureResponse = {
function isLlmProtocol (line 98) | function isLlmProtocol(value: unknown): value is LlmProtocolType {
function readProbeFailureCode (line 102) | function readProbeFailureCode(value: unknown): string {
function shouldProbeModelLlmProtocol (line 106) | function shouldProbeModelLlmProtocol(params: {
function shouldReprobeModelLlmProtocol (line 113) | function shouldReprobeModelLlmProtocol(params: {
function probeModelLlmProtocolViaApi (line 124) | async function probeModelLlmProtocolViaApi(params: {
function pickConfiguredLlmModel (line 159) | function pickConfiguredLlmModel(params: {
function buildProviderConnectionPayload (line 169) | function buildProviderConnectionPayload(params: {
function buildCustomPricingFromModelForm (line 197) | function buildCustomPricingFromModelForm(
function toProviderCardModelType (line 284) | function toProviderCardModelType(type: CustomModel['type']): ProviderCar...
type UseProviderCardStateResult (line 290) | interface UseProviderCardStateResult {
function getAssistantSavedModelLabel (line 346) | function getAssistantSavedModelLabel(event: AssistantSavedEvent): string {
function useProviderCardState (line 354) | function useProviderCardState({
FILE: src/app/[locale]/profile/components/api-config/provider-card/types.ts
type ProviderCardDefaultModels (line 4) | interface ProviderCardDefaultModels {
type ProviderCardProps (line 16) | interface ProviderCardProps {
type ModelFormState (line 35) | interface ModelFormState {
type ProviderCardModelType (line 45) | type ProviderCardModelType = 'llm' | 'image' | 'video' | 'audio'
type ProviderCardGroupedModels (line 47) | type ProviderCardGroupedModels = Partial<Record<ProviderCardModelType, C...
type ProviderCardTranslator (line 49) | type ProviderCardTranslator = (
constant VERIFIABLE_PROVIDER_KEYS (line 58) | const VERIFIABLE_PROVIDER_KEYS = new Set([
FILE: src/app/[locale]/profile/components/api-config/types.ts
type Provider (line 16) | interface Provider {
type LlmCustomPricing (line 27) | interface LlmCustomPricing {
type MediaCustomPricing (line 32) | interface MediaCustomPricing {
type CustomModelPricing (line 38) | interface CustomModelPricing {
type CustomModel (line 45) | interface CustomModel {
type PricingDisplayItem (line 67) | interface PricingDisplayItem {
type PricingDisplayMap (line 75) | type PricingDisplayMap = Record<string, PricingDisplayItem>
type ApiConfig (line 78) | interface ApiConfig {
type PresetModel (line 89) | type PresetModel = Omit<CustomModel, 'enabled' | 'modelKey' | 'price'>
constant PRESET_MODELS (line 92) | const PRESET_MODELS: PresetModel[] = [
constant PRESET_COMING_SOON_MODEL_KEYS (line 187) | const PRESET_COMING_SOON_MODEL_KEYS = new Set<string>([
function isPresetComingSoonModel (line 191) | function isPresetComingSoonModel(provider: string, modelId: string): boo...
function isPresetComingSoonModelKey (line 195) | function isPresetComingSoonModelKey(modelKey: string): boolean {
constant PRESET_PROVIDERS (line 200) | const PRESET_PROVIDERS: Omit<Provider, 'apiKey' | 'hasApiKey'>[] = [
constant ZH_PROVIDER_NAME_MAP (line 210) | const ZH_PROVIDER_NAME_MAP: Record<string, string> = {
function isZhLocale (line 218) | function isZhLocale(locale?: string): boolean {
function resolvePresetProviderName (line 222) | function resolvePresetProviderName(providerId: string, fallbackName: str...
function getProviderKey (line 230) | function getProviderKey(providerId?: string): string {
function getProviderDisplayName (line 241) | function getProviderDisplayName(providerId?: string, locale?: string): s...
function encodeModelKey (line 255) | function encodeModelKey(provider: string, modelId: string): string {
function parseModelKey (line 264) | function parseModelKey(key: string | undefined | null): { provider: stri...
function matchesModelKey (line 280) | function matchesModelKey(key: string | undefined | null, provider: strin...
type TutorialStep (line 287) | interface TutorialStep {
type ProviderTutorial (line 293) | interface ProviderTutorial {
constant PROVIDER_TUTORIALS (line 300) | const PROVIDER_TUTORIALS: ProviderTutorial[] = [
function getProviderTutorial (line 400) | function getProviderTutorial(providerId: string): ProviderTutorial | und...
function getGoogleCompatiblePresetModels (line 410) | function getGoogleCompatiblePresetModels(providerId: string): PresetMode...
FILE: src/app/[locale]/profile/page.tsx
function ProfilePage (line 10) | function ProfilePage() {
FILE: src/app/[locale]/providers.tsx
function Providers (line 7) | function Providers({ children }: { children: React.ReactNode }) {
FILE: src/app/[locale]/workspace/[projectId]/components/Sidebar.tsx
type Episode (line 8) | interface Episode {
type SidebarProps (line 15) | interface SidebarProps {
function Sidebar (line 28) | function Sidebar({
FILE: src/app/[locale]/workspace/[projectId]/episode-selection.ts
type EpisodeLike (line 1) | interface EpisodeLike {
function resolveSelectedEpisodeId (line 5) | function resolveSelectedEpisodeId(
FILE: src/app/[locale]/workspace/[projectId]/hooks/useProject.ts
type RefreshScope (line 12) | type RefreshScope = 'all' | 'project' | 'assets'
type RefreshMode (line 19) | type RefreshMode = 'full' | 'silent'
type RefreshOptions (line 24) | interface RefreshOptions {
function useProject (line 37) | function useProject(projectId: string) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/NovelPromotionWorkspace.tsx
function NovelPromotionWorkspaceContent (line 17) | function NovelPromotionWorkspaceContent(props: NovelPromotionWorkspacePr...
function NovelPromotionWorkspace (line 175) | function NovelPromotionWorkspace(props: NovelPromotionWorkspaceProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/StageNavigation.tsx
type StageNavigationProps (line 9) | interface StageNavigationProps {
function StageNavigation (line 24) | function StageNavigation({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/WorkspaceProvider.tsx
type RefreshScope (line 16) | type RefreshScope = 'all' | 'assets' | 'project'
type RefreshOptions (line 17) | type RefreshOptions = { scope?: string; mode?: string }
type TaskEventListener (line 18) | type TaskEventListener = (event: SSEEvent) => void
type WorkspaceContextValue (line 20) | interface WorkspaceContextValue {
type WorkspaceProviderProps (line 28) | interface WorkspaceProviderProps {
function WorkspaceProvider (line 36) | function WorkspaceProvider({ projectId, episodeId, children }: Workspace...
function useWorkspaceProvider (line 95) | function useWorkspaceProvider() {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/WorkspaceStageRuntimeContext.tsx
type WorkspaceStageVideoModelOption (line 8) | interface WorkspaceStageVideoModelOption {
type WorkspaceStageRuntimeValue (line 17) | interface WorkspaceStageRuntimeValue {
type WorkspaceStageRuntimeProviderProps (line 63) | interface WorkspaceStageRuntimeProviderProps {
function WorkspaceStageRuntimeProvider (line 68) | function WorkspaceStageRuntimeProvider({ value, children }: WorkspaceSta...
function useWorkspaceStageRuntime (line 76) | function useWorkspaceStageRuntime() {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/AssetLibrary.tsx
type AssetLibraryProps (line 19) | interface AssetLibraryProps {
function AssetLibrary (line 24) | function AssetLibrary({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/AssetsStage.tsx
type AssetsStageProps (line 47) | interface AssetsStageProps {
function AssetsStage (line 57) | function AssetsStage({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/ConfigStage.tsx
function ConfigStage (line 7) | function ConfigStage() {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/NovelInputStage.tsx
function RatioIcon (line 20) | function RatioIcon({ ratio, size = 24, selected = false }: { ratio: stri...
function RatioSelector (line 28) | function RatioSelector({
function StyleSelector (line 115) | function StyleSelector({
type NovelInputStageProps (line 188) | interface NovelInputStageProps {
function NovelInputStage (line 209) | function NovelInputStage({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/PanelEditForm.tsx
type CharacterAppearance (line 10) | interface CharacterAppearance {
type PanelEditData (line 16) | interface PanelEditData {
type PanelEditFormProps (line 34) | interface PanelEditFormProps {
function PanelEditForm (line 47) | function PanelEditForm({
type CharacterPickerModalProps (line 76) | interface CharacterPickerModalProps {
function CharacterPickerModal (line 83) | function CharacterPickerModal({
type LocationPickerModalProps (line 137) | interface LocationPickerModalProps {
function LocationPickerModal (line 144) | function LocationPickerModal({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/PromptsStage.tsx
function PromptsStage (line 7) | function PromptsStage(props: PromptsStageShellProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/ScriptStage.tsx
function ScriptStage (line 8) | function ScriptStage() {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/SmartImportWizard.tsx
type SmartImportWizardProps (line 14) | interface SmartImportWizardProps {
function SmartImportWizard (line 21) | function SmartImportWizard({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/StoryboardStage.tsx
function StoryboardStage (line 8) | function StoryboardStage() {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/VideoStage.tsx
function VideoStage (line 7) | function VideoStage(props: VideoStageShellProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/VideoStageRoute.tsx
function VideoStageRoute (line 9) | function VideoStageRoute() {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/VoiceStage.tsx
function VoiceStage (line 7) | function VoiceStage(props: VoiceStageShellProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/VoiceStageRoute.tsx
function VoiceStageRoute (line 7) | function VoiceStageRoute() {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/WorkspaceAssetLibraryModal.tsx
type WorkspaceAssetLibraryModalProps (line 8) | interface WorkspaceAssetLibraryModalProps {
function WorkspaceAssetLibraryModal (line 23) | function WorkspaceAssetLibraryModal({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/WorkspaceHeaderShell.tsx
type EpisodeSummary (line 9) | interface EpisodeSummary {
type UserModelOption (line 20) | interface UserModelOption {
type UserModelsPayload (line 28) | interface UserModelsPayload {
type WorkspaceHeaderShellProps (line 35) | interface WorkspaceHeaderShellProps {
function WorkspaceHeaderShell (line 82) | function WorkspaceHeaderShell({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/WorkspaceRunStreamConsoles.tsx
type RunStreamStep (line 6) | type RunStreamStep = {
type RunStreamState (line 13) | type RunStreamState = {
type WorkspaceRunStreamConsolesProps (line 37) | interface WorkspaceRunStreamConsolesProps {
function WorkspaceRunStreamConsoles (line 47) | function WorkspaceRunStreamConsoles({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/WorkspaceStageContent.tsx
type WorkspaceStageContentProps (line 9) | interface WorkspaceStageContentProps {
function WorkspaceStageContent (line 13) | function WorkspaceStageContent({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/WorkspaceTopActions.tsx
type WorkspaceTopActionsProps (line 7) | interface WorkspaceTopActionsProps {
function WorkspaceTopActions (line 16) | function WorkspaceTopActions({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/AddLocationModal.tsx
type AddLocationModalProps (line 14) | interface AddLocationModalProps {
function getErrorMessage (line 20) | function getErrorMessage(error: unknown, fallback: string): string {
function getErrorStatus (line 29) | function getErrorStatus(error: unknown): number | null {
function AddLocationModal (line 46) | function AddLocationModal({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/AssetToolbar.tsx
type AssetToolbarProps (line 16) | interface AssetToolbarProps {
function AssetToolbar (line 30) | function AssetToolbar({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/AssetsStageModals.tsx
type EditingAppearanceState (line 17) | interface EditingAppearanceState {
type EditingLocationState (line 26) | interface EditingLocationState {
type LocationImageEditModalState (line 32) | interface LocationImageEditModalState {
type CharacterImageEditModalState (line 36) | interface CharacterImageEditModalState {
type VoiceDesignCharacterState (line 40) | interface VoiceDesignCharacterState {
type EditingProfileState (line 45) | interface EditingProfileState {
type AssetsStageModalsProps (line 51) | interface AssetsStageModalsProps {
function AssetsStageModals (line 86) | function AssetsStageModals({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/AssetsStageStatusOverlays.tsx
type ToastType (line 7) | type ToastType = 'success' | 'warning' | 'error'
type ToastState (line 9) | interface ToastState {
type AssetsStageStatusOverlaysProps (line 14) | interface AssetsStageStatusOverlaysProps {
function AssetsStageStatusOverlays (line 24) | function AssetsStageStatusOverlays({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/CharacterCard.tsx
type CharacterCardProps (line 27) | interface CharacterCardProps {
function CharacterCard (line 53) | function CharacterCard({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/CharacterEditModal.tsx
type CharacterEditModalProps (line 8) | interface CharacterEditModalProps {
function CharacterEditModal (line 24) | function CharacterEditModal({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/CharacterProfileCard.tsx
type CharacterProfileCardProps (line 14) | interface CharacterProfileCardProps {
constant ROLE_LEVEL_COLORS (line 26) | const ROLE_LEVEL_COLORS = {
constant ROLE_LEVELS (line 33) | const ROLE_LEVELS = ['S', 'A', 'B', 'C', 'D'] as const
type RoleLevel (line 34) | type RoleLevel = (typeof ROLE_LEVELS)[number]
function isRoleLevel (line 36) | function isRoleLevel(value: string): value is RoleLevel {
function CharacterProfileCard (line 40) | function CharacterProfileCard({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/CharacterProfileDialog.tsx
type CharacterProfileDialogProps (line 15) | interface CharacterProfileDialogProps {
constant ROLE_LEVELS (line 24) | const ROLE_LEVELS: RoleLevel[] = ['S', 'A', 'B', 'C', 'D']
constant COSTUME_TIERS (line 25) | const COSTUME_TIERS: CostumeTier[] = [5, 4, 3, 2, 1]
function CharacterProfileDialog (line 27) | function CharacterProfileDialog({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/CharacterSection.tsx
type CharacterSectionProps (line 21) | interface CharacterSectionProps {
function CharacterSection (line 52) | function CharacterSection({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/ImageEditModal.tsx
type ImageEditModalProps (line 13) | interface ImageEditModalProps {
function ImageEditModal (line 20) | function ImageEditModal({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/LocationCard.tsx
type LocationCardProps (line 24) | interface LocationCardProps {
function LocationCard (line 41) | function LocationCard({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/LocationEditModal.tsx
type LocationEditModalProps (line 5) | interface LocationEditModalProps {
function LocationEditModal (line 17) | function LocationEditModal({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/LocationSection.tsx
type LocationSectionProps (line 17) | interface LocationSectionProps {
function LocationSection (line 39) | function LocationSection({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/UnconfirmedProfilesSection.tsx
type UnconfirmedProfilesSectionProps (line 9) | interface UnconfirmedProfilesSectionProps {
function UnconfirmedProfilesSection (line 25) | function UnconfirmedProfilesSection({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/VoiceSettings.tsx
type VoiceSettingsProps (line 14) | interface VoiceSettingsProps {
function getErrorMessage (line 25) | function getErrorMessage(error: unknown, fallback: string): string {
function VoiceSettings (line 34) | function VoiceSettings({
type UploadedVoiceResult (line 209) | type UploadedVoiceResult = { audioUrl?: string }
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/character-card/CharacterCardActions.tsx
type CharacterCardActionsProps (line 11) | type CharacterCardActionsProps =
function CharacterCardActions (line 35) | function CharacterCardActions(props: CharacterCardActionsProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/character-card/CharacterCardGallery.tsx
type CharacterCardGalleryProps (line 11) | type CharacterCardGalleryProps =
function CharacterCardGallery (line 39) | function CharacterCardGallery(props: CharacterCardGalleryProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/character-card/CharacterCardHeader.tsx
type CharacterCardHeaderProps (line 6) | type CharacterCardHeaderProps =
function CharacterCardHeader (line 22) | function CharacterCardHeader(props: CharacterCardHeaderProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/hooks/useAssetModals.ts
type EditingAppearance (line 15) | interface EditingAppearance {
type EditingLocation (line 24) | interface EditingLocation {
type ImageEditModal (line 30) | interface ImageEditModal {
type CharacterImageEditModal (line 36) | interface CharacterImageEditModal {
type UseAssetModalsProps (line 43) | interface UseAssetModalsProps {
function useAssetModals (line 47) | function useAssetModals({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/hooks/useAssetsCopyFromHub.ts
type ToastType (line 8) | type ToastType = 'success' | 'warning' | 'error'
type ShowToast (line 10) | type ShowToast = (message: string, type?: ToastType, duration?: number) ...
type GlobalCopyTarget (line 12) | type GlobalCopyTarget = {
type UseAssetsCopyFromHubParams (line 17) | interface UseAssetsCopyFromHubParams {
function useAssetsCopyFromHub (line 25) | function useAssetsCopyFromHub({ projectId, onRefresh, showToast }: UseAs...
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/hooks/useAssetsGlobalActions.ts
type ToastType (line 8) | type ToastType = 'success' | 'warning' | 'error'
type ShowToast (line 10) | type ShowToast = (message: string, type?: ToastType, duration?: number) ...
type TranslateValues (line 11) | type TranslateValues = Record<string, string | number | Date>
type Translate (line 12) | type Translate = (key: string, values?: TranslateValues) => string
type UseAssetsGlobalActionsParams (line 14) | interface UseAssetsGlobalActionsParams {
function useAssetsGlobalActions (line 25) | function useAssetsGlobalActions({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/hooks/useAssetsImageEdit.ts
type ToastType (line 15) | type ToastType = 'success' | 'warning' | 'error'
type ShowToast (line 17) | type ShowToast = (message: string, type?: ToastType, duration?: number) ...
type TranslateValues (line 18) | type TranslateValues = Record<string, string | number | Date>
type Translate (line 19) | type Translate = (key: string, values?: TranslateValues) => string
type EditingAppearanceState (line 21) | interface EditingAppearanceState {
type EditingLocationState (line 27) | interface EditingLocationState {
type LocationImageEditState (line 31) | interface LocationImageEditState {
type CharacterImageEditState (line 37) | interface CharacterImageEditState {
type UseAssetsImageEditParams (line 44) | interface UseAssetsImageEditParams {
function useAssetsImageEdit (line 61) | function useAssetsImageEdit({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/hooks/useBatchGeneration.helpers.ts
constant MANUAL_REGENERATE_TIMEOUT_MS (line 4) | const MANUAL_REGENERATE_TIMEOUT_MS = 90_000
type ManualRegenerationBaseline (line 6) | type ManualRegenerationBaseline = {
function createCharacterGroupSignature (line 11) | function createCharacterGroupSignature(appearance: CharacterAppearance) {
function createLocationGroupSignature (line 19) | function createLocationGroupSignature(location: Location) {
function createManualKeyBaseline (line 30) | function createManualKeyBaseline(
function isAppearanceTaskRunning (line 78) | function isAppearanceTaskRunning(appearance: CharacterAppearance) {
function shouldResolveManualKey (line 82) | function shouldResolveManualKey(
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/hooks/useBatchGeneration.ts
type UseBatchGenerationProps (line 24) | interface UseBatchGenerationProps {
function useBatchGeneration (line 30) | function useBatchGeneration({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/hooks/useCharacterActions.ts
type UseCharacterActionsProps (line 28) | interface UseCharacterActionsProps {
function getErrorMessage (line 33) | function getErrorMessage(error: unknown, fallback: string): string {
function useCharacterActions (line 42) | function useCharacterActions({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/hooks/useLocationActions.ts
type UseLocationActionsProps (line 25) | interface UseLocationActionsProps {
function getErrorMessage (line 30) | function getErrorMessage(error: unknown, fallback: string): string {
function useLocationActions (line 39) | function useLocationActions({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/hooks/useProfileManagement.ts
type UseProfileManagementProps (line 21) | interface UseProfileManagementProps {
function useProfileManagement (line 26) | function useProfileManagement({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/hooks/useTTSGeneration.ts
type VoiceDesignCharacter (line 20) | interface VoiceDesignCharacter {
type UseTTSGenerationProps (line 26) | interface UseTTSGenerationProps {
function getErrorMessage (line 30) | function getErrorMessage(error: unknown, fallback: string): string {
function useTTSGeneration (line 39) | function useTTSGeneration({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/location-card/LocationCardActions.tsx
type LocationCardActionsProps (line 10) | type LocationCardActionsProps =
function LocationCardActions (line 28) | function LocationCardActions(props: LocationCardActionsProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/location-card/LocationCardHeader.tsx
type LocationCardHeaderProps (line 6) | type LocationCardHeaderProps =
function LocationCardHeader (line 22) | function LocationCardHeader(props: LocationCardHeaderProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/assets/location-card/LocationImageList.tsx
type SelectionImage (line 16) | type SelectionImage = {
type LocationImageListProps (line 25) | type LocationImageListProps =
function LocationImageList (line 52) | function LocationImageList(props: LocationImageListProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/prompts-stage/PromptEditorPanel.tsx
type PromptEditorPanelProps (line 5) | interface PromptEditorPanelProps {
function PromptEditorPanel (line 9) | function PromptEditorPanel({ runtime }: PromptEditorPanelProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/prompts-stage/PromptListCardView.tsx
type PromptListCardViewProps (line 13) | interface PromptListCardViewProps {
function PromptListCardView (line 16) | function PromptListCardView({ runtime }: PromptListCardViewProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/prompts-stage/PromptListPanel.tsx
type PromptListPanelProps (line 8) | interface PromptListPanelProps {
function PromptListPanel (line 12) | function PromptListPanel({ runtime }: PromptListPanelProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/prompts-stage/PromptListTableView.tsx
type PromptListTableViewProps (line 10) | interface PromptListTableViewProps {
function PromptListTableView (line 14) | function PromptListTableView({ runtime }: PromptListTableViewProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/prompts-stage/PromptsStageLayout.tsx
function PromptsStageLayout (line 10) | function PromptsStageLayout(props: PromptsStageShellProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/prompts-stage/PromptsStageShell.tsx
function PromptsStageShell (line 7) | function PromptsStageShell(props: PromptsStageShellProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/prompts-stage/runtime/hooks/usePromptAiModifyFlow.ts
type ModifyShotPromptResult (line 8) | interface ModifyShotPromptResult {
type PromptAiModifier (line 12) | interface PromptAiModifier {
type UsePromptAiModifyFlowParams (line 21) | interface UsePromptAiModifyFlowParams {
function usePromptAiModifyFlow (line 33) | function usePromptAiModifyFlow({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/prompts-stage/runtime/hooks/usePromptAppendFlow.ts
type UsePromptAppendFlowParams (line 8) | interface UsePromptAppendFlowParams {
function usePromptAppendFlow (line 13) | function usePromptAppendFlow({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/prompts-stage/runtime/hooks/usePromptAssetMention.ts
type UsePromptAssetMentionParams (line 10) | interface UsePromptAssetMentionParams {
function usePromptAssetMention (line 17) | function usePromptAssetMention({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/prompts-stage/runtime/hooks/usePromptDraftByShot.ts
function usePromptDraftByShot (line 9) | function usePromptDraftByShot() {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/prompts-stage/runtime/hooks/usePromptEditorRuntime.ts
type UsePromptEditorRuntimeParams (line 11) | interface UsePromptEditorRuntimeParams {
function usePromptEditorRuntime (line 18) | function usePromptEditorRuntime({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/prompts-stage/runtime/promptStageRuntime.types.ts
type PromptsStageShellProps (line 5) | interface PromptsStageShellProps {
type LocationAssetWithImages (line 22) | type LocationAssetWithImages = AssetLibraryLocation & {
type PromptAssetReference (line 32) | interface PromptAssetReference {
type PromptShotEditState (line 39) | interface PromptShotEditState {
type PromptEditingTarget (line 46) | interface PromptEditingTarget {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/prompts-stage/runtime/promptStageRuntime.utils.ts
function getErrorMessage (line 5) | function getErrorMessage(error: unknown, fallback: string): string {
function parseImagePrompt (line 9) | function parseImagePrompt(imagePrompt: string | null) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/prompts-stage/runtime/promptStageRuntimeCore.tsx
function usePromptStageActions (line 22) | function usePromptStageActions({
type PromptStageRuntime (line 139) | type PromptStageRuntime = ReturnType<typeof usePromptStageActions>
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/script-view/ScriptViewAssetsPanel.tsx
type Clip (line 12) | interface Clip {
type ScriptViewAssetsPanelProps (line 17) | interface ScriptViewAssetsPanelProps {
function setsEqual (line 49) | function setsEqual<T>(left: Set<T>, right: Set<T>): boolean {
function parseAppearanceKey (line 57) | function parseAppearanceKey(key: string): { characterId: string; appeara...
function parseLocationNames (line 66) | function parseLocationNames(raw: string | null | undefined): string[] {
function fuzzyMatchLocationName (line 84) | function fuzzyMatchLocationName(clipLocName: string, libraryLocName: str...
function readTrimmedLabel (line 94) | function readTrimmedLabel(value: string | undefined, fallback: string): ...
function getAppearancePreviewUrl (line 99) | function getAppearancePreviewUrl(appearance: CharacterAppearance): strin...
function ScriptViewAssetsPanel (line 116) | function ScriptViewAssetsPanel({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/script-view/ScriptViewRuntime.tsx
type Clip (line 24) | interface Clip {
type Panel (line 34) | interface Panel {
type Storyboard (line 40) | interface Storyboard {
type ScriptViewProps (line 46) | interface ScriptViewProps {
function toTranslationValues (line 60) | function toTranslationValues(values?: Record<string, unknown>) {
function ScriptView (line 64) | function ScriptView({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/script-view/ScriptViewScriptPanel.tsx
type Clip (line 7) | interface Clip {
type ScreenplayContentItem (line 17) | type ScreenplayContentItem =
type ScreenplayScene (line 22) | interface ScreenplayScene {
type ScreenplayData (line 33) | interface ScreenplayData {
function parseScreenplay (line 37) | function parseScreenplay(value: string | null | undefined): ScreenplayDa...
type ScriptViewScriptPanelProps (line 51) | interface ScriptViewScriptPanelProps {
function EditableText (line 63) | function EditableText({
function ScriptViewScriptPanel (line 115) | function ScriptViewScriptPanel({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/script-view/SpotlightCards.tsx
type SpotlightCharCardProps (line 13) | type SpotlightCharCardProps = {
function SpotlightCharCard (line 22) | function SpotlightCharCard({
function getSelectedLocationImage (line 179) | function getSelectedLocationImage(location: Location) {
type SpotlightLocationCardProps (line 188) | type SpotlightLocationCardProps = {
function SpotlightLocationCard (line 196) | function SpotlightLocationCard({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/script-view/asset-state-utils.ts
type ClipLike (line 4) | interface ClipLike {
function getPrimaryAppearance (line 9) | function getPrimaryAppearance(char: Character): CharacterAppearance | un...
function getSelectedAppearances (line 13) | function getSelectedAppearances(
function processCharacterInClip (line 37) | function processCharacterInClip(params: {
function processLocationInClip (line 150) | function processLocationInClip(params: {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/script-view/clip-asset-utils.ts
type ClipAssetSource (line 1) | type ClipAssetSource = {
type ParsedClipAssets (line 6) | type ParsedClipAssets = {
function fuzzyMatchLocation (line 12) | function fuzzyMatchLocation(clipLocName: string, libraryLocName: string)...
function parseClipAssets (line 29) | function parseClipAssets(clip: ClipAssetSource): ParsedClipAssets {
function getAllClipsAssets (line 86) | function getAllClipsAssets(clips: ClipAssetSource[]) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/smart-import/hooks/useWizardState.ts
type TranslateValues (line 15) | type TranslateValues = Record<string, string | number | Date>
type Translate (line 16) | type Translate = (key: string, values?: TranslateValues) => string
type UseWizardStateParams (line 18) | interface UseWizardStateParams {
function useWizardState (line 25) | function useWizardState({ projectId, importStatus, onImportComplete, t }...
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/smart-import/steps/StepConfirm.tsx
type StepConfirmProps (line 8) | interface StepConfirmProps {
function StepConfirm (line 17) | function StepConfirm({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/smart-import/steps/StepMapping.tsx
type StepMappingProps (line 7) | interface StepMappingProps {
function StepMapping (line 22) | function StepMapping({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/smart-import/steps/StepParse.tsx
function StepParse (line 5) | function StepParse() {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/smart-import/steps/StepSource.tsx
type StepSourceProps (line 8) | interface StepSourceProps {
function StepSource (line 21) | function StepSource({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/smart-import/types.ts
type SplitEpisode (line 1) | interface SplitEpisode {
type WizardStage (line 9) | type WizardStage = 'select' | 'analyzing' | 'preview'
type DeleteConfirmState (line 11) | interface DeleteConfirmState {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/AIDataModal.tsx
function AIDataModal (line 18) | function AIDataModal({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/AIDataModal.types.ts
type PhotographyCharacter (line 3) | interface PhotographyCharacter {
type PhotographyRules (line 10) | interface PhotographyRules {
type ActingCharacter (line 22) | interface ActingCharacter {
type ActingNotes (line 27) | interface ActingNotes {
type AIDataSavePayload (line 32) | interface AIDataSavePayload {
type AIDataModalProps (line 41) | interface AIDataModalProps {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/AIDataModalFormPane.tsx
type AIDataModalFormPaneProps (line 9) | interface AIDataModalFormPaneProps {
function AIDataModalFormPane (line 28) | function AIDataModalFormPane({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/AIDataModalPreviewPane.tsx
type AIDataModalPreviewPaneProps (line 5) | interface AIDataModalPreviewPaneProps {
function AIDataModalPreviewPane (line 10) | function AIDataModalPreviewPane({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/CandidateSelector.tsx
type CandidateSelectorProps (line 10) | interface CandidateSelectorProps {
function CandidateSelector (line 22) | function CandidateSelector({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/ImageEditModal.tsx
type ImageEditModalProps (line 13) | interface ImageEditModalProps {
function ImageEditModal (line 20) | function ImageEditModal({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/ImageEditModalAssetPicker.tsx
type ImageEditModalAssetPickerProps (line 10) | interface ImageEditModalAssetPickerProps {
function ImageEditModalAssetPicker (line 21) | function ImageEditModalAssetPicker({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/ImageEditModalSelectedAssets.tsx
type ImageEditModalSelectedAssetsProps (line 9) | interface ImageEditModalSelectedAssetsProps {
function ImageEditModalSelectedAssets (line 16) | function ImageEditModalSelectedAssets({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/ImageSection.tsx
type PanelCandidateData (line 13) | interface PanelCandidateData {
type ImageSectionProps (line 18) | interface ImageSectionProps {
function ImageSection (line 41) | function ImageSection({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/ImageSectionActionButtons.tsx
type ImageSectionActionButtonsProps (line 11) | interface ImageSectionActionButtonsProps {
function ImageSectionActionButtons (line 24) | function ImageSectionActionButtons({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/ImageSectionCandidateMode.tsx
type PanelCandidateData (line 10) | interface PanelCandidateData {
type ImageSectionCandidateModeProps (line 15) | interface ImageSectionCandidateModeProps {
function ImageSectionCandidateMode (line 25) | function ImageSectionCandidateMode({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/InsertPanelButton.tsx
type InsertPanelButtonProps (line 10) | interface InsertPanelButtonProps {
function InsertPanelButton (line 15) | function InsertPanelButton({ onClick, disabled }: InsertPanelButtonProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/InsertPanelModal.tsx
type PanelInfo (line 16) | interface PanelInfo {
type InsertPanelModalProps (line 23) | interface InsertPanelModalProps {
function InsertPanelModal (line 32) | function InsertPanelModal({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/PanelActionButtons.tsx
type PanelActionButtonsProps (line 12) | interface PanelActionButtonsProps {
function PanelActionButtons (line 19) | function PanelActionButtons({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/PanelCard.tsx
type PanelCandidateData (line 11) | interface PanelCandidateData {
type PanelCardProps (line 16) | interface PanelCardProps {
function PanelCard (line 53) | function PanelCard({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/PanelVariantModal.tsx
type PanelVariantModalProps (line 14) | interface PanelVariantModalProps {
function PanelVariantModal (line 26) | function PanelVariantModal({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/PanelVariantModal.types.ts
type ShotVariantSuggestion (line 1) | interface ShotVariantSuggestion {
type PanelInfo (line 11) | interface PanelInfo {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/PanelVariantModalCustomOptions.tsx
type PanelVariantModalCustomOptionsProps (line 4) | interface PanelVariantModalCustomOptionsProps {
function PanelVariantModalCustomOptions (line 14) | function PanelVariantModalCustomOptions({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/PanelVariantModalSuggestionList.tsx
type PanelVariantModalSuggestionListProps (line 7) | interface PanelVariantModalSuggestionListProps {
function PanelVariantModalSuggestionList (line 19) | function PanelVariantModalSuggestionList({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/ScreenplayDisplay.tsx
type ScreenplayScene (line 7) | interface ScreenplayScene {
type Screenplay (line 25) | interface Screenplay {
type ScreenplayDisplayProps (line 31) | interface ScreenplayDisplayProps {
function ScreenplayDisplay (line 36) | function ScreenplayDisplay({ screenplay, originalContent }: ScreenplayDi...
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/StoryboardCanvas.tsx
type StoryboardCanvasProps (line 13) | interface StoryboardCanvasProps {
function StoryboardCanvas (line 75) | function StoryboardCanvas({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/StoryboardGroup.tsx
function StoryboardGroup (line 19) | function StoryboardGroup({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/StoryboardGroup.types.ts
type StoryboardGroupProps (line 7) | interface StoryboardGroupProps {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/StoryboardGroupActions.tsx
type StoryboardGroupActionsProps (line 10) | interface StoryboardGroupActionsProps {
function StoryboardGroupActions (line 22) | function StoryboardGroupActions({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/StoryboardGroupDialogs.tsx
type PanelRuntimeSnapshot (line 7) | interface PanelRuntimeSnapshot {
type VariantPanelRuntimeSnapshot (line 14) | interface VariantPanelRuntimeSnapshot extends PanelRuntimeSnapshot {
type StoryboardGroupDialogsProps (line 18) | interface StoryboardGroupDialogsProps {
function StoryboardGroupDialogs (line 32) | function StoryboardGroupDialogs({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/StoryboardGroupFailedAlert.tsx
type StoryboardGroupFailedAlertProps (line 5) | interface StoryboardGroupFailedAlertProps {
function StoryboardGroupFailedAlert (line 12) | function StoryboardGroupFailedAlert({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/StoryboardGroupHeader.tsx
type StoryboardGroupHeaderProps (line 7) | interface StoryboardGroupHeaderProps {
function StoryboardGroupHeader (line 18) | function StoryboardGroupHeader({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/StoryboardHeader.tsx
type StoryboardHeaderProps (line 8) | interface StoryboardHeaderProps {
function StoryboardHeader (line 20) | function StoryboardHeader({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/StoryboardPanelList.tsx
type StoryboardPanelListProps (line 11) | interface StoryboardPanelListProps {
function StoryboardPanelList (line 46) | function StoryboardPanelList({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/StoryboardStageShell.tsx
type StoryboardStageShellProps (line 8) | interface StoryboardStageShellProps {
function StoryboardStageShell (line 16) | function StoryboardStageShell({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/StoryboardToolbar.tsx
type StoryboardToolbarProps (line 10) | interface StoryboardToolbarProps {
function StoryboardToolbar (line 25) | function StoryboardToolbar({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/contracts.ts
type StoryboardPanelUpdateContract (line 4) | interface StoryboardPanelUpdateContract {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/image-generation-runtime.ts
type StoryboardImageMutationResult (line 3) | interface StoryboardImageMutationResult {
function isAbortError (line 8) | function isAbortError(error: unknown): boolean {
function getStoryboardPanels (line 13) | function getStoryboardPanels(storyboard: NovelPromotionStoryboard): Nove...
function updatePanelImageUrlInStoryboards (line 17) | function updatePanelImageUrlInStoryboards(
function createPanelMap (line 33) | function createPanelMap(storyboards: NovelPromotionStoryboard[]): Map<st...
function reconcileSubmittingPanelImageIds (line 44) | function reconcileSubmittingPanelImageIds(
function reconcileModifyingPanelIds (line 71) | function reconcileModifyingPanelIds(
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/panel-candidate-runtime.ts
type PanelCandidateData (line 6) | interface PanelCandidateData {
type CandidateStateLike (line 11) | interface CandidateStateLike {
type PanelCandidateSystemLike (line 18) | interface PanelCandidateSystemLike {
function sameStringArray (line 29) | function sameStringArray(left: string[], right: string[]) {
function parseCandidateImages (line 37) | function parseCandidateImages(candidateImagesStr: string): string[] | nu...
function clearIfExists (line 48) | function clearIfExists(system: PanelCandidateSystemLike, panelId: string) {
function ensurePanelCandidatesInitialized (line 55) | function ensurePanelCandidatesInitialized(
function getPanelCandidatesFromRuntime (line 95) | function getPanelCandidatesFromRuntime(
function getErrorMessage (line 127) | function getErrorMessage(error: unknown, fallback: string): string {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/panel-operations-shared.ts
function isAbortError (line 6) | function isAbortError(err: unknown): boolean {
function getErrorMessage (line 10) | function getErrorMessage(error: unknown, fallback: string): string {
function getStoryboardPanels (line 14) | function getStoryboardPanels(storyboard: NovelPromotionStoryboard): Nove...
type InsertPanelMutationResult (line 18) | interface InsertPanelMutationResult {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/panel-save-coordinator.ts
type PanelSaveStatus (line 5) | type PanelSaveStatus = 'idle' | 'saving' | 'error'
type PanelSaveState (line 7) | interface PanelSaveState {
type PanelSaveRuntime (line 12) | interface PanelSaveRuntime {
type PanelSaveCoordinatorCallbacks (line 21) | interface PanelSaveCoordinatorCallbacks {
function clonePanelEditData (line 28) | function clonePanelEditData(data: PanelEditData): PanelEditData {
class PanelSaveCoordinator (line 35) | class PanelSaveCoordinator {
method constructor (line 40) | constructor(callbacks: PanelSaveCoordinatorCallbacks) {
method updateCallbacks (line 44) | updateCallbacks(callbacks: PanelSaveCoordinatorCallbacks) {
method queue (line 48) | queue(
method retry (line 62) | retry(panelId: string, snapshotOverride?: PanelEditData | null): Promi...
method clear (line 70) | clear(panelId: string) {
method ensureRuntime (line 74) | private ensureRuntime(panelId: string): PanelSaveRuntime {
method startProcessor (line 88) | private startProcessor(panelId: string, runtime: PanelSaveRuntime): Pr...
method executeSaveAttempt (line 106) | private async executeSaveAttempt(panelId: string, runtime: PanelSaveRu...
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/storyboard-panel-asset-utils.ts
function getErrorMessage (line 12) | function getErrorMessage(error: unknown, fallback: string): string {
type BuildDefaultAssetsForClipParams (line 16) | interface BuildDefaultAssetsForClipParams {
function buildDefaultAssetsForClip (line 23) | function buildDefaultAssetsForClip({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/storyboard-state-utils.ts
function getStoryboardPanels (line 5) | function getStoryboardPanels(storyboard: NovelPromotionStoryboard): Nove...
function sortStoryboardsByClipOrder (line 9) | function sortStoryboardsByClipOrder(
function areStoryboardsEquivalent (line 21) | function areStoryboardsEquivalent(
function buildStoryboardSyncSignature (line 30) | function buildStoryboardSyncSignature(
function computeStoryboardStartIndex (line 56) | function computeStoryboardStartIndex(
function computeTotalPanels (line 70) | function computeTotalPanels(storyboards: NovelPromotionStoryboard[]): nu...
function formatClipTitle (line 74) | function formatClipTitle(clip: NovelPromotionClip | null | undefined): s...
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/useAIDataModalState.ts
type UseAIDataModalStateParams (line 12) | interface UseAIDataModalStateParams {
type DirtyField (line 23) | type DirtyField =
type AIDataModalDraftState (line 31) | interface AIDataModalDraftState {
function normalizeText (line 40) | function normalizeText(value: string | null): string {
function clonePhotographyRules (line 44) | function clonePhotographyRules(rules: PhotographyRules | null): Photogra...
function normalizeActingNotes (line 53) | function normalizeActingNotes(notes: ActingNotes | ActingCharacter[] | n...
function createAIDataModalDraftState (line 58) | function createAIDataModalDraftState(params: {
function mergeAIDataModalDraftStateByDirty (line 76) | function mergeAIDataModalDraftStateByDirty(
function useAIDataModalState (line 91) | function useAIDataModalState({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/useImageGeneration.ts
type SelectedAsset (line 25) | interface SelectedAsset {
type UseStoryboardImageGenerationProps (line 34) | interface UseStoryboardImageGenerationProps {
function useStoryboardImageGeneration (line 41) | function useStoryboardImageGeneration({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/usePanelCandidates.ts
type UsePanelCandidatesProps (line 22) | interface UsePanelCandidatesProps {
type SelectPanelCandidateResult (line 28) | interface SelectPanelCandidateResult {
function usePanelCandidates (line 32) | function usePanelCandidates({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/usePanelCrudActions.ts
type UsePanelCrudActionsProps (line 26) | interface UsePanelCrudActionsProps {
function usePanelCrudActions (line 34) | function usePanelCrudActions({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/usePanelEpisodeCachePatch.ts
type EpisodeDataCache (line 8) | type EpisodeDataCache = Record<string, unknown> & {
function isEpisodeDataCache (line 12) | function isEpisodeDataCache(value: unknown): value is EpisodeDataCache {
type UsePanelEpisodeCachePatchParams (line 16) | interface UsePanelEpisodeCachePatchParams {
function usePanelEpisodeCachePatch (line 21) | function usePanelEpisodeCachePatch({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/usePanelImageDownload.ts
type DownloadImagesMutationLike (line 8) | interface DownloadImagesMutationLike {
type UsePanelImageDownloadParams (line 12) | interface UsePanelImageDownloadParams {
function usePanelImageDownload (line 18) | function usePanelImageDownload({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/usePanelImageModification.ts
type ModifyPanelMutationLike (line 16) | interface ModifyPanelMutationLike {
type UsePanelImageModificationParams (line 26) | interface UsePanelImageModificationParams {
function usePanelImageModification (line 36) | function usePanelImageModification({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/usePanelImageRegeneration.ts
type RegeneratePanelMutationLike (line 12) | interface RegeneratePanelMutationLike {
type UsePanelImageRegenerationParams (line 16) | interface UsePanelImageRegenerationParams {
function usePanelImageRegeneration (line 28) | function usePanelImageRegeneration({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/usePanelInsertActions.ts
type UsePanelInsertActionsProps (line 10) | interface UsePanelInsertActionsProps {
function usePanelInsertActions (line 15) | function usePanelInsertActions({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/usePanelOperations.ts
type UsePanelOperationsProps (line 9) | interface UsePanelOperationsProps {
function usePanelOperations (line 15) | function usePanelOperations({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/usePanelVariant.ts
type VariantData (line 16) | interface VariantData {
type VariantOptions (line 24) | interface VariantOptions {
type VariantModalState (line 29) | interface VariantModalState {
type UsePanelVariantProps (line 37) | interface UsePanelVariantProps {
function usePanelVariant (line 44) | function usePanelVariant({ projectId, episodeId, setLocalStoryboards }: ...
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/useStoryboardAiDataRuntime.ts
type AIDataPanelRef (line 10) | interface AIDataPanelRef {
type PhotographyPlanMutation (line 15) | interface PhotographyPlanMutation {
type ActingNotesMutation (line 19) | interface ActingNotesMutation {
type UseStoryboardAiDataRuntimeParams (line 23) | interface UseStoryboardAiDataRuntimeParams {
function parseJsonSafely (line 34) | function parseJsonSafely(value: unknown, logLabel: string) {
function useStoryboardAiDataRuntime (line 44) | function useStoryboardAiDataRuntime({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/useStoryboardBatchPanelGeneration.ts
type UseStoryboardBatchPanelGenerationProps (line 10) | interface UseStoryboardBatchPanelGenerationProps {
function useStoryboardBatchPanelGeneration (line 18) | function useStoryboardBatchPanelGeneration({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/useStoryboardGroupActions.ts
type UseStoryboardGroupActionsProps (line 15) | interface UseStoryboardGroupActionsProps {
function useStoryboardGroupActions (line 21) | function useStoryboardGroupActions({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/useStoryboardGroupTaskErrors.ts
type UseStoryboardGroupTaskErrorsParams (line 8) | interface UseStoryboardGroupTaskErrorsParams {
function useStoryboardGroupTaskErrors (line 17) | function useStoryboardGroupTaskErrors({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/useStoryboardInsertVariantRuntime.ts
type PanelRuntimeSnapshot (line 7) | interface PanelRuntimeSnapshot {
type VariantPanelRuntimeSnapshot (line 14) | interface VariantPanelRuntimeSnapshot extends PanelRuntimeSnapshot {
type UseStoryboardInsertVariantRuntimeParams (line 18) | interface UseStoryboardInsertVariantRuntimeParams {
function useStoryboardInsertVariantRuntime (line 31) | function useStoryboardInsertVariantRuntime({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/useStoryboardModalRuntime.ts
type AssetPickerPanelRef (line 10) | interface AssetPickerPanelRef {
type AIDataPanelRef (line 15) | interface AIDataPanelRef {
type PhotographyPlanMutation (line 20) | interface PhotographyPlanMutation {
type ActingNotesMutation (line 24) | interface ActingNotesMutation {
type UseStoryboardModalRuntimeParams (line 28) | interface UseStoryboardModalRuntimeParams {
type StoryboardPanelReference (line 52) | interface StoryboardPanelReference {
function findPanelById (line 57) | function findPanelById(
function useStoryboardModalRuntime (line 74) | function useStoryboardModalRuntime({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/useStoryboardPanelAssetActions.ts
type UseStoryboardPanelAssetActionsProps (line 11) | interface UseStoryboardPanelAssetActionsProps {
function useStoryboardPanelAssetActions (line 61) | function useStoryboardPanelAssetActions({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/useStoryboardStageController.ts
type UseStoryboardStageControllerProps (line 24) | interface UseStoryboardStageControllerProps {
function useStoryboardStageController (line 32) | function useStoryboardStageController({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/useStoryboardStageStatus.ts
type UseStoryboardStageStatusProps (line 6) | interface UseStoryboardStageStatusProps {
function useStoryboardStageStatus (line 11) | function useStoryboardStageStatus({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/useStoryboardStageUiState.ts
function useStoryboardStageUiState (line 5) | function useStoryboardStageUiState() {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/useStoryboardState.ts
type StoryboardPanel (line 16) | interface StoryboardPanel {
type UseStoryboardStateProps (line 36) | interface UseStoryboardStateProps {
function useStoryboardState (line 43) | function useStoryboardState({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/hooks/useStoryboardTaskAwareStoryboards.ts
type TaskTarget (line 7) | interface TaskTarget {
type UseStoryboardTaskAwareStoryboardsProps (line 16) | interface UseStoryboardTaskAwareStoryboardsProps {
function buildStoryboardTextTargets (line 22) | function buildStoryboardTextTargets(storyboards: NovelPromotionStoryboar...
function buildPanelTargets (line 49) | function buildPanelTargets(storyboards: NovelPromotionStoryboard[], type...
function useStoryboardTaskAwareStoryboards (line 88) | function useStoryboardTaskAwareStoryboards({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/storyboard/index.tsx
type StoryboardStageProps (line 14) | interface StoryboardStageProps {
function StoryboardStage (line 25) | function StoryboardStage({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video-stage/VideoRenderPanel.tsx
type VideoRenderPanelProps (line 7) | interface VideoRenderPanelProps {
function VideoRenderPanel (line 74) | function VideoRenderPanel({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video-stage/VideoStageLayout.tsx
function VideoStageLayout (line 7) | function VideoStageLayout(props: VideoStageShellProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video-stage/VideoStageShell.tsx
function VideoStageShell (line 7) | function VideoStageShell(props: VideoStageShellProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video-stage/VideoTimelinePanel.tsx
type VoiceLineSummary (line 5) | interface VoiceLineSummary {
type VideoTimelinePanelProps (line 9) | interface VideoTimelinePanelProps {
function VideoTimelinePanel (line 20) | function VideoTimelinePanel({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/FirstLastFramePanel.tsx
type FirstLastFramePanelProps (line 12) | interface FirstLastFramePanelProps {
function FirstLastFramePanel (line 49) | function FirstLastFramePanel({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/VideoPanelCard.tsx
function VideoPanelCard (line 8) | function VideoPanelCard(props: VideoPanelCardShellProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/VideoPromptModal.tsx
type VideoPromptModalProps (line 7) | interface VideoPromptModalProps {
function VideoPromptModal (line 16) | function VideoPromptModal({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/VideoToolbar.tsx
type VideoToolbarProps (line 7) | interface VideoToolbarProps {
function VideoToolbar (line 21) | function VideoToolbar({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/VideoPanelCardBody.tsx
type VideoPanelCardBodyProps (line 8) | interface VideoPanelCardBodyProps {
function VideoPanelCardBody (line 12) | function VideoPanelCardBody({ runtime }: VideoPanelCardBodyProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/VideoPanelCardFooter.tsx
type VideoPanelCardFooterProps (line 4) | interface VideoPanelCardFooterProps {
function VideoPanelCardFooter (line 8) | function VideoPanelCardFooter({ runtime }: VideoPanelCardFooterProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/VideoPanelCardHeader.tsx
type VideoPanelCardHeaderProps (line 8) | interface VideoPanelCardHeaderProps {
function VideoPanelCardHeader (line 12) | function VideoPanelCardHeader({ runtime }: VideoPanelCardHeaderProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/VideoPanelCardLayout.tsx
function VideoPanelCardLayout (line 11) | function VideoPanelCardLayout(props: VideoPanelCardShellProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/runtime/hooks/usePanelLipSync.ts
type UsePanelLipSyncParams (line 6) | interface UsePanelLipSyncParams {
function usePanelLipSync (line 12) | function usePanelLipSync({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/runtime/hooks/usePanelPlayer.ts
type UsePanelPlayerParams (line 4) | interface UsePanelPlayerParams {
function usePanelPlayer (line 13) | function usePanelPlayer({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/runtime/hooks/usePanelPromptEditor.ts
type UsePanelPromptEditorParams (line 3) | interface UsePanelPromptEditorParams {
function usePanelPromptEditor (line 9) | function usePanelPromptEditor({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/runtime/hooks/usePanelTaskStatus.ts
type UsePanelTaskStatusParams (line 5) | interface UsePanelTaskStatusParams {
function usePanelTaskStatus (line 11) | function usePanelTaskStatus({ panel, hasVisibleBaseVideo, tCommon }: Use...
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/runtime/hooks/usePanelVideoModel.ts
type UsePanelVideoModelParams (line 11) | interface UsePanelVideoModelParams {
type CapabilityField (line 17) | interface CapabilityField {
function toFieldLabel (line 28) | function toFieldLabel(field: string): string {
function parseByOptionType (line 32) | function parseByOptionType(
function isRecord (line 41) | function isRecord(value: unknown): value is Record<string, unknown> {
function isGenerationOptionValue (line 45) | function isGenerationOptionValue(value: unknown): value is VideoGenerati...
function readSelectionForModel (line 49) | function readSelectionForModel(
function usePanelVideoModel (line 66) | function usePanelVideoModel({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/runtime/hooks/usePanelVoiceManager.ts
type UsePanelVoiceManagerParams (line 11) | interface UsePanelVoiceManagerParams {
function usePanelVoiceManager (line 19) | function usePanelVoiceManager({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/runtime/shared.ts
constant EMPTY_RUNNING_VOICE_LINE_IDS (line 1) | const EMPTY_RUNNING_VOICE_LINE_IDS: Set<string> = new Set()
function getErrorMessage (line 3) | function getErrorMessage(error: unknown): string {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/runtime/videoPanelRuntimeCore.tsx
function useVideoPanelActions (line 13) | function useVideoPanelActions({
type VideoPanelRuntime (line 164) | type VideoPanelRuntime = ReturnType<typeof useVideoPanelActions>
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/panel-card/types.ts
type VideoPanelCardShellProps (line 4) | interface VideoPanelCardShellProps {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/video/types.ts
type VideoModelOption (line 6) | interface VideoModelOption {
type VideoGenerationMode (line 16) | type VideoGenerationMode = 'normal' | 'firstlastframe'
type TextPanel (line 18) | interface TextPanel {
type Panel (line 32) | interface Panel {
type Storyboard (line 63) | interface Storyboard {
type Clip (line 74) | interface Clip {
type VideoPanel (line 81) | interface VideoPanel {
type MatchedVoiceLine (line 104) | interface MatchedVoiceLine {
type FirstLastFrameParams (line 114) | interface FirstLastFrameParams {
type VideoGenerationOptionValue (line 121) | type VideoGenerationOptionValue = string | number | boolean
type VideoGenerationOptions (line 122) | type VideoGenerationOptions = Record<string, VideoGenerationOptionValue>
type BatchVideoGenerationParams (line 124) | interface BatchVideoGenerationParams {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/voice-stage/VoiceControlPanel.tsx
type BindablePanelOption (line 10) | interface BindablePanelOption {
type VoiceControlPanelProps (line 17) | interface VoiceControlPanelProps {
function VoiceControlPanel (line 55) | function VoiceControlPanel({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/voice-stage/VoiceLineList.tsx
type VoiceLineListProps (line 6) | interface VoiceLineListProps {
function VoiceLineList (line 24) | function VoiceLineList({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/voice-stage/VoiceStageLayout.tsx
function VoiceStageLayout (line 7) | function VoiceStageLayout(props: VoiceStageShellProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/voice-stage/VoiceStageShell.tsx
function VoiceStageShell (line 7) | function VoiceStageShell(props: VoiceStageShellProps) {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/voice/EmbeddedVoiceToolbar.tsx
type EmbeddedVoiceToolbarProps (line 6) | interface EmbeddedVoiceToolbarProps {
function EmbeddedVoiceToolbar (line 20) | function EmbeddedVoiceToolbar({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/voice/EmotionSettingsPanel.tsx
type EmotionSettingsPanelProps (line 7) | interface EmotionSettingsPanelProps {
function EmotionSettingsPanel (line 16) | function EmotionSettingsPanel({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/voice/EmptyVoiceState.tsx
type EmptyVoiceStateProps (line 7) | interface EmptyVoiceStateProps {
function EmptyVoiceState (line 12) | function EmptyVoiceState({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/voice/SpeakerVoiceBindingDialog.tsx
type BindingTab (line 12) | type BindingTab = 'select' | 'upload' | 'design'
type SpeakerVoiceBindingDialogProps (line 14) | interface SpeakerVoiceBindingDialogProps {
function SpeakerVoiceBindingDialog (line 28) | function SpeakerVoiceBindingDialog({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/voice/SpeakerVoiceStatus.tsx
type SpeakerVoiceStatusProps (line 4) | interface SpeakerVoiceStatusProps {
function SpeakerVoiceStatus (line 16) | function SpeakerVoiceStatus({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/voice/VoiceDesignDialog.tsx
type VoiceDesignDialogProps (line 9) | interface VoiceDesignDialogProps {
function VoiceDesignDialog (line 18) | function VoiceDesignDialog({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/voice/VoiceLineCard.tsx
type VoiceLine (line 9) | interface VoiceLine {
type VoiceLineCardProps (line 24) | interface VoiceLineCardProps {
function VoiceLineCard (line 40) | function VoiceLineCard({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/components/voice/VoiceToolbar.tsx
type VoiceToolbarProps (line 6) | interface VoiceToolbarProps {
function VoiceToolbar (line 22) | function VoiceToolbar({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/hooks/useNovelPromotionWorkspaceController.ts
function useNovelPromotionWorkspaceController (line 23) | function useNovelPromotionWorkspaceController({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/hooks/useRebuildConfirm.ts
type RebuildActionType (line 6) | type RebuildActionType = 'storyToScript' | 'scriptToStoryboard'
type RebuildConfirmContext (line 8) | interface RebuildConfirmContext {
type DownstreamCheckResult (line 14) | interface DownstreamCheckResult {
type StoryboardStats (line 20) | type StoryboardStats = {
function hasDownstreamStoryboardData (line 25) | function hasDownstreamStoryboardData(stats: StoryboardStats): boolean {
type StoryboardLike (line 29) | interface StoryboardLike {
type UseRebuildConfirmParams (line 33) | interface UseRebuildConfirmParams {
function useRebuildConfirm (line 40) | function useRebuildConfirm({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/hooks/useWorkspaceAssetLibraryShell.ts
type RefreshOptions (line 5) | type RefreshOptions = { scope?: string; mode?: string }
type RouterLike (line 7) | interface RouterLike {
type SearchParamsLike (line 11) | interface SearchParamsLike {
type UseWorkspaceAssetLibraryShellParams (line 16) | interface UseWorkspaceAssetLibraryShellParams {
function useWorkspaceAssetLibraryShell (line 23) | function useWorkspaceAssetLibraryShell({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/hooks/useWorkspaceConfigActions.ts
type UseWorkspaceConfigActionsParams (line 11) | interface UseWorkspaceConfigActionsParams {
function useWorkspaceConfigActions (line 17) | function useWorkspaceConfigActions({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/hooks/useWorkspaceEpisodeStageData.ts
type EpisodeStagePayload (line 7) | interface EpisodeStagePayload {
function useWorkspaceEpisodeStageData (line 14) | function useWorkspaceEpisodeStageData() {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/hooks/useWorkspaceExecution.ts
type UseWorkspaceExecutionParams (line 11) | interface UseWorkspaceExecutionParams {
function isAbortError (line 24) | function isAbortError(err: unknown): boolean {
function getErrorMessage (line 29) | function getErrorMessage(err: unknown): string {
function isRunStreamTimeoutMessage (line 34) | function isRunStreamTimeoutMessage(message: string): boolean {
function readSessionBoolean (line 38) | function readSessionBoolean(key: string): boolean {
function writeSessionBoolean (line 47) | function writeSessionBoolean(key: string, value: boolean) {
function useWorkspaceExecution (line 60) | function useWorkspaceExecution({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/hooks/useWorkspaceModalEscape.ts
type UseWorkspaceModalEscapeParams (line 5) | interface UseWorkspaceModalEscapeParams {
function useWorkspaceModalEscape (line 14) | function useWorkspaceModalEscape({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/hooks/useWorkspaceProjectSnapshot.ts
function parseCapabilitySelections (line 7) | function parseCapabilitySelections(raw: unknown): CapabilitySelections {
function useWorkspaceProjectSnapshot (line 22) | function useWorkspaceProjectSnapshot({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/hooks/useWorkspaceStageNavigation.ts
type EpisodeLike (line 5) | interface EpisodeLike {
type StoryboardLike (line 10) | interface StoryboardLike {
type CapsuleNavItem (line 14) | interface CapsuleNavItem {
type UseWorkspaceStageNavigationParams (line 23) | interface UseWorkspaceStageNavigationParams {
function useWorkspaceStageNavigation (line 31) | function useWorkspaceStageNavigation({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/hooks/useWorkspaceStageRuntime.ts
type UseWorkspaceStageRuntimeParams (line 9) | interface UseWorkspaceStageRuntimeParams {
function useWorkspaceStageRuntime (line 59) | function useWorkspaceStageRuntime({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/hooks/useWorkspaceUserModels.ts
type UserModelOption (line 9) | interface UserModelOption {
type UserModelsPayload (line 18) | interface UserModelsPayload {
function useWorkspaceUserModels (line 26) | function useWorkspaceUserModels() {
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/hooks/useWorkspaceVideoActions.ts
type UseWorkspaceVideoActionsParams (line 8) | interface UseWorkspaceVideoActionsParams {
function isAbortError (line 14) | function isAbortError(err: unknown): boolean {
function getErrorMessage (line 19) | function getErrorMessage(err: unknown): string {
function assertClipUpdateData (line 24) | function assertClipUpdateData(data: unknown): asserts data is Record<str...
function useWorkspaceVideoActions (line 30) | function useWorkspaceVideoActions({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/hooks/workspace-controller-view-model.ts
type StoryToScriptStreamState (line 14) | type StoryToScriptStreamState = ReturnType<typeof useStoryToScriptRunStr...
type ScriptToStoryboardStreamState (line 15) | type ScriptToStoryboardStreamState = ReturnType<typeof useScriptToStoryb...
type ProjectSnapshotInput (line 17) | interface ProjectSnapshotInput {
type BuildWorkspaceControllerViewModelParams (line 36) | interface BuildWorkspaceControllerViewModelParams {
function buildWorkspaceControllerViewModel (line 137) | function buildWorkspaceControllerViewModel({
FILE: src/app/[locale]/workspace/[projectId]/modes/novel-promotion/types.ts
type Episode (line 8) | interface Episode {
type NovelPromotionWorkspaceProps (line 23) | interface NovelPromotionWorkspaceProps {
FILE: src/app/[locale]/workspace/[projectId]/page.tsx
constant VALID_STAGES (line 23) | const VALID_STAGES = ['config', 'script', 'assets', 'text-storyboard', '...
type Stage (line 24) | type Stage = typeof VALID_STAGES[number]
type Episode (line 26) | interface Episode {
type NovelPromotionData (line 37) | type NovelPromotionData = {
function ProjectDetailPage (line 45) | function ProjectDetailPage() {
FILE: src/app/[locale]/workspace/asset-hub/components/AddLocationModal.tsx
type AddLocationModalProps (line 13) | interface AddLocationModalProps {
function AddLocationModal (line 28) | function AddLocationModal({ folderId, onClose, onSuccess }: AddLocationM...
FILE: src/app/[locale]/workspace/asset-hub/components/AssetGrid.tsx
type Character (line 15) | interface Character {
type Location (line 35) | interface Location {
type Voice (line 51) | interface Voice {
type AssetGridProps (line 64) | interface AssetGridProps {
function AssetGrid (line 88) | function AssetGrid({
FILE: src/app/[locale]/workspace/asset-hub/components/CharacterCard.tsx
type Appearance (line 27) | interface Appearance {
type Character (line 42) | interface Character {
type CharacterCardProps (line 50) | interface CharacterCardProps {
function CharacterCard (line 59) | function CharacterCard({ character, onImageClick, onImageEdit, onVoiceDe...
FILE: src/app/[locale]/workspace/asset-hub/components/CharacterEditModal.tsx
type CharacterEditModalProps (line 22) | interface CharacterEditModalProps {
function CharacterEditModal (line 32) | function CharacterEditModal({
FILE: src/app/[locale]/workspace/asset-hub/components/FolderModal.tsx
type Folder (line 7) | interface Folder {
type FolderModalProps (line 12) | interface FolderModalProps {
function FolderModal (line 23) | function FolderModal({ folder, onClose, onSave }: FolderModalProps) {
FILE: src/app/[locale]/workspace/asset-hub/components/FolderSidebar.tsx
type Folder (line 6) | interface Folder {
type FolderSidebarProps (line 11) | interface FolderSidebarProps {
function FolderSidebar (line 37) | function FolderSidebar({
FILE: src/app/[locale]/workspace/asset-hub/components/LocationCard.tsx
type LocationImage (line 28) | interface LocationImage {
type Location (line 40) | interface Location {
type LocationCardProps (line 49) | interface LocationCardProps {
function LocationCard (line 56) | function LocationCard({ location, onImageClick, onImageEdit, onEdit }: L...
FILE: src/app/[locale]/workspace/asset-hub/components/LocationEditModal.tsx
type LocationEditModalProps (line 22) | interface LocationEditModalProps {
function LocationEditModal (line 32) | function LocationEditModal({
FILE: src/app/[locale]/workspace/asset-hub/components/VoiceCard.tsx
type Voice (line 8) | interface Voice {
type VoiceCardProps (line 21) | interface VoiceCardProps {
function VoiceCard (line 28) | function VoiceCard({ voice, onSelect, isSelected = false, selectionMode ...
FILE: src/app/[locale]/workspace/asset-hub/components/VoiceCreationModal.tsx
function VoiceCreationModal (line 7) | function VoiceCreationModal(props: VoiceCreationModalShellProps) {
FILE: src/app/[locale]/workspace/asset-hub/components/VoiceDesignDialog.tsx
type VoiceDesignDialogProps (line 9) | interface VoiceDesignDialogProps {
function VoiceDesignDialog (line 17) | function VoiceDesignDialog({
FILE: src/app/[locale]/workspace/asset-hub/components/VoicePickerDialog.tsx
type Voice (line 12) | interface Voice {
type VoicePickerDialogProps (line 25) | interface VoicePickerDialogProps {
function VoicePickerDialog (line 31) | function VoicePickerDialog({ isOpen, onClose, onSelect }: VoicePickerDia...
FILE: src/app/[locale]/workspace/asset-hub/components/VoiceSettings.tsx
type VoiceSettingsProps (line 14) | interface VoiceSettingsProps {
function VoiceSettings (line 25) | function VoiceSettings({
FILE: src/app/[locale]/workspace/asset-hub/components/voice-creation/VoiceCreationForm.tsx
type VoiceCreationFormProps (line 6) | interface VoiceCreationFormProps {
function VoiceCreationForm (line 11) | function VoiceCreationForm({ runtime, children }: VoiceCreationFormProps) {
FILE: src/app/[locale]/workspace/asset-hub/components/voice-creation/VoiceCreationModalLayout.tsx
function VoiceCreationModalLayout (line 10) | function VoiceCreationModalLayout(props: VoiceCreationModalShellProps) {
FILE: src/app/[locale]/workspace/asset-hub/components/voice-creation/VoiceCreationModalShell.tsx
function VoiceCreationModalShell (line 7) | function VoiceCreationModalShell(props: VoiceCreationModalShellProps) {
FILE: src/app/[locale]/workspace/asset-hub/components/voice-creation/VoicePreviewSection.tsx
type VoicePreviewSectionProps (line 5) | interface VoicePreviewSectionProps {
function VoicePreviewSection (line 9) | function VoicePreviewSection({ runtime }: VoicePreviewSectionProps) {
FILE: src/app/[locale]/workspace/asset-hub/components/voice-creation/hooks/useVoiceCreation.tsx
type VoiceCreationModalShellProps (line 17) | interface VoiceCreationModalShellProps {
type CreationMode (line 26) | type CreationMode = 'design' | 'upload'
function useVoiceCreation (line 28) | function useVoiceCreation({ isOpen, folderId, onClose, onSuccess, initia...
type VoiceCreationRuntime (line 339) | type VoiceCreationRuntime = ReturnType<typeof useVoiceCreation>
FILE: src/app/[locale]/workspace/asset-hub/page.tsx
function AssetHubPage (line 34) | function AssetHubPage() {
FILE: src/app/[locale]/workspace/page.tsx
type ProjectStats (line 15) | interface ProjectStats {
type Project (line 23) | interface Project {
type Pagination (line 33) | interface Pagination {
constant PAGE_SIZE (line 40) | const PAGE_SIZE = 7 // 加上新建项目按钮正好8个,4列布局下2行
constant DEFAULT_BILLING_CURRENCY (line 41) | const DEFAULT_BILLING_CURRENCY = 'CNY'
function formatProjectCost (line 43) | function formatProjectCost(amount: number, currency = DEFAULT_BILLING_CU...
function WorkspacePage (line 48) | function WorkspacePage() {
FILE: src/app/api/admin/download-logs/route.ts
constant GET (line 9) | const GET = apiHandler(async () => {
FILE: src/app/api/asset-hub/ai-design-character/route.ts
constant POST (line 12) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/ai-design-location/route.ts
constant POST (line 12) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/ai-modify-character/route.ts
constant POST (line 13) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/ai-modify-location/route.ts
constant POST (line 13) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/appearances/route.ts
type AppearanceBody (line 9) | interface AppearanceBody {
type GlobalCharacterAppearanceSummary (line 17) | interface GlobalCharacterAppearanceSummary {
type GlobalCharacterRecord (line 25) | interface GlobalCharacterRecord {
type AssetHubAppearancesDb (line 29) | interface AssetHubAppearancesDb {
constant POST (line 45) | const POST = apiHandler(async (request: NextRequest) => {
constant PATCH (line 103) | const PATCH = apiHandler(async (request: NextRequest) => {
constant DELETE (line 175) | const DELETE = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/character-voice/route.ts
type VoiceDesignPayload (line 7) | interface VoiceDesignPayload {
type CharacterVoiceJsonBody (line 12) | interface CharacterVoiceJsonBody {
type AssetHubCharacterVoiceDb (line 20) | interface AssetHubCharacterVoiceDb {
constant POST (line 31) | const POST = apiHandler(async (request: NextRequest) => {
constant PATCH (line 133) | const PATCH = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/characters/[characterId]/appearances/[appearanceIndex]/route.ts
constant PATCH (line 9) | const PATCH = apiHandler(async (
constant POST (line 88) | const POST = apiHandler(async (
constant DELETE (line 148) | const DELETE = apiHandler(async (
FILE: src/app/api/asset-hub/characters/[characterId]/route.ts
constant GET (line 11) | const GET = apiHandler(async (
constant PATCH (line 35) | const PATCH = apiHandler(async (
constant DELETE (line 88) | const DELETE = apiHandler(async (
FILE: src/app/api/asset-hub/characters/route.ts
function toObject (line 13) | function toObject(value: unknown): Record<string, unknown> {
constant GET (line 19) | const GET = apiHandler(async (request: NextRequest) => {
constant POST (line 49) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/folders/[folderId]/route.ts
constant PATCH (line 7) | const PATCH = apiHandler(async (
constant DELETE (line 43) | const DELETE = apiHandler(async (
FILE: src/app/api/asset-hub/folders/route.ts
constant GET (line 7) | const GET = apiHandler(async () => {
constant POST (line 22) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/generate-image/route.ts
function toNumber (line 19) | function toNumber(value: unknown) {
function toObject (line 24) | function toObject(value: unknown): Record<string, unknown> {
function normalizeString (line 29) | function normalizeString(value: unknown): string {
function resolveRequestedArtStyle (line 33) | function resolveRequestedArtStyle(body: Record<string, unknown>): string...
function resolveStoredArtStyle (line 45) | async function resolveStoredArtStyle(input: {
constant POST (line 93) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/locations/[locationId]/route.ts
constant GET (line 7) | const GET = apiHandler(async (
constant PATCH (line 31) | const PATCH = apiHandler(async (
constant DELETE (line 78) | const DELETE = apiHandler(async (
FILE: src/app/api/asset-hub/locations/route.ts
constant GET (line 10) | const GET = apiHandler(async (request: NextRequest) => {
constant POST (line 40) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/modify-image/route.ts
function toNumber (line 17) | function toNumber(value: unknown) {
function toObject (line 22) | function toObject(value: unknown): Record<string, unknown> {
constant POST (line 27) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/picker/route.ts
constant GET (line 16) | const GET = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/reference-to-character/route.ts
function parseReferenceImages (line 8) | function parseReferenceImages(body: Record<string, unknown>): string[] {
constant POST (line 20) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/select-image/route.ts
type SelectImageBody (line 11) | interface SelectImageBody {
constant POST (line 23) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/undo-image/route.ts
type UndoImageBody (line 8) | interface UndoImageBody {
type GlobalCharacterAppearanceRecord (line 14) | interface GlobalCharacterAppearanceRecord {
type GlobalLocationImageRecord (line 25) | interface GlobalLocationImageRecord {
type GlobalLocationRecord (line 33) | interface GlobalLocationRecord {
type AssetHubUndoDb (line 37) | interface AssetHubUndoDb {
constant POST (line 54) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/update-asset-label/route.ts
constant POST (line 16) | const POST = apiHandler(async (request: NextRequest) => {
function updateImageLabel (line 120) | async function updateImageLabel(imageUrl: string, newLabelText: string):...
FILE: src/app/api/asset-hub/upload-image/route.ts
type GlobalCharacterAppearanceRecord (line 10) | interface GlobalCharacterAppearanceRecord {
type GlobalLocationImageRecord (line 19) | interface GlobalLocationImageRecord {
type GlobalLocationRecord (line 25) | interface GlobalLocationRecord {
type AssetHubUploadDb (line 29) | interface AssetHubUploadDb {
constant POST (line 47) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/upload-temp/route.ts
constant POST (line 11) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/voice-design/route.ts
constant POST (line 15) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/voices/[id]/route.ts
constant DELETE (line 7) | const DELETE = apiHandler(async (
constant PATCH (line 38) | const PATCH = apiHandler(async (
FILE: src/app/api/asset-hub/voices/route.ts
constant GET (line 9) | const GET = apiHandler(async (request: NextRequest) => {
constant POST (line 38) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/asset-hub/voices/upload/route.ts
constant POST (line 11) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/auth/[...nextauth]/route.ts
function handlePost (line 19) | async function handlePost(req: NextRequest, ctx: { params: Promise<{ nex...
function handleGet (line 46) | function handleGet(req: NextRequest, ctx: { params: Promise<{ nextauth: ...
FILE: src/app/api/auth/register/route.ts
constant POST (line 8) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/cos/image/route.ts
constant GET (line 4) | const GET = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/files/[...path]/route.ts
constant UPLOAD_DIR (line 13) | const UPLOAD_DIR = process.env.UPLOAD_DIR || './data/uploads'
constant MIME_TYPES (line 16) | const MIME_TYPES: Record<string, string> = {
function getMimeType (line 32) | function getMimeType(filePath: string): string {
function GET (line 37) | async function GET(
FILE: src/app/api/novel-promotion/[projectId]/ai-create-character/route.ts
constant POST (line 9) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/ai-create-location/route.ts
constant POST (line 9) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/ai-modify-appearance/route.ts
constant POST (line 7) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/ai-modify-location/route.ts
constant POST (line 7) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/ai-modify-shot-prompt/route.ts
constant POST (line 7) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/analyze-global/route.ts
constant POST (line 10) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/analyze-shot-variants/route.ts
constant POST (line 7) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/analyze/route.ts
constant POST (line 7) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/assets/route.ts
constant GET (line 11) | const GET = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/character-profile/batch-confirm/route.ts
constant POST (line 11) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/character-profile/confirm/route.ts
constant POST (line 11) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/character-voice/route.ts
constant PATCH (line 13) | const PATCH = apiHandler(async (
constant POST (line 49) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/character/appearance/route.ts
constant POST (line 14) | const POST = apiHandler(async (
constant PATCH (line 80) | const PATCH = apiHandler(async (
constant DELETE (line 154) | const DELETE = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/character/confirm-selection/route.ts
constant POST (line 19) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/character/route.ts
function toObject (line 15) | function toObject(value: unknown): Record<string, unknown> {
function normalizeString (line 20) | function normalizeString(value: unknown): string {
constant PATCH (line 25) | const PATCH = apiHandler(async (
constant DELETE (line 61) | const DELETE = apiHandler(async (
constant POST (line 117) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/cleanup-unselected-images/route.ts
constant POST (line 14) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/clips/[clipId]/route.ts
constant PATCH (line 11) | const PATCH = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/clips/route.ts
constant POST (line 11) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/copy-from-global/route.ts
type GlobalCharacterAppearanceSource (line 9) | interface GlobalCharacterAppearanceSource {
type GlobalCharacterSource (line 19) | interface GlobalCharacterSource {
type GlobalLocationImageSource (line 27) | interface GlobalLocationImageSource {
type GlobalLocationSource (line 34) | interface GlobalLocationSource {
type GlobalVoiceSource (line 40) | interface GlobalVoiceSource {
type CopyFromGlobalDb (line 47) | interface CopyFromGlobalDb {
constant POST (line 65) | const POST = apiHandler(async (
function copyCharacterFromGlobal (line 98) | async function copyCharacterFromGlobal(db: CopyFromGlobalDb, userId: str...
function copyLocationFromGlobal (line 192) | async function copyLocationFromGlobal(db: CopyFromGlobalDb, userId: stri...
function copyVoiceFromGlobal (line 283) | async function copyVoiceFromGlobal(db: CopyFromGlobalDb, userId: string,...
FILE: src/app/api/novel-promotion/[projectId]/download-images/route.ts
type PanelData (line 10) | interface PanelData {
type StoryboardData (line 16) | interface StoryboardData {
type ClipData (line 21) | interface ClipData {
type EpisodeData (line 25) | interface EpisodeData {
constant GET (line 30) | const GET = apiHandler(async (
type ImageItem (line 93) | interface ImageItem {
method start (line 151) | start(controller) {
function processImages (line 159) | async function processImages() {
FILE: src/app/api/novel-promotion/[projectId]/download-videos/route.ts
type PanelData (line 10) | interface PanelData {
type StoryboardData (line 17) | interface StoryboardData {
type ClipData (line 23) | interface ClipData {
type EpisodeData (line 27) | interface EpisodeData {
constant POST (line 32) | const POST = apiHandler(async (
type VideoItem (line 100) | interface VideoItem {
FILE: src/app/api/novel-promotion/[projectId]/download-voices/route.ts
constant GET (line 10) | const GET = apiHandler(async (
method start (line 57) | start(controller) {
function processVoices (line 65) | async function processVoices() {
FILE: src/app/api/novel-promotion/[projectId]/editor/route.ts
constant GET (line 10) | const GET = apiHandler(async (
constant PUT (line 49) | const PUT = apiHandler(async (
constant DELETE (line 102) | const DELETE = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/episodes/[episodeId]/route.ts
constant GET (line 13) | const GET = apiHandler(async (
constant PATCH (line 65) | const PATCH = apiHandler(async (
constant DELETE (line 100) | const DELETE = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/episodes/batch/route.ts
type BatchEpisode (line 10) | interface BatchEpisode {
constant POST (line 16) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/episodes/route.ts
constant GET (line 9) | const GET = apiHandler(async (
constant POST (line 31) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/episodes/split-by-markers/route.ts
constant POST (line 14) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/episodes/split/route.ts
constant POST (line 10) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/generate-character-image/route.ts
function toObject (line 10) | function toObject(value: unknown): Record<string, unknown> {
function normalizeString (line 15) | function normalizeString(value: unknown): string {
constant POST (line 24) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/generate-image/route.ts
function toObject (line 19) | function toObject(value: unknown): Record<string, unknown> {
function toNumber (line 24) | function toNumber(value: unknown) {
function normalizeString (line 29) | function normalizeString(value: unknown): string {
function resolveArtStyle (line 33) | function resolveArtStyle(body: Record<string, unknown>): ArtStyleValue |...
constant POST (line 45) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/generate-video/route.ts
function isRecord (line 19) | function isRecord(value: unknown): value is Record<string, unknown> {
function toVideoRuntimeSelections (line 23) | function toVideoRuntimeSelections(value: unknown): Record<string, Capabi...
function resolveVideoGenerationMode (line 35) | function resolveVideoGenerationMode(payload: unknown): 'normal' | 'first...
function resolveVideoModelKeyFromPayload (line 40) | function resolveVideoModelKeyFromPayload(payload: Record<string, unknown...
function requireVideoModelKeyFromPayload (line 51) | function requireVideoModelKeyFromPayload(payload: unknown): string {
function validateFirstLastFrameModel (line 61) | function validateFirstLastFrameModel(input: unknown) {
function validateVideoCapabilityCombination (line 87) | async function validateVideoCapabilityCombination(input: {
function buildVideoPanelBillingInfoOrThrow (line 143) | function buildVideoPanelBillingInfoOrThrow(payload: unknown) {
constant POST (line 171) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/insert-panel/route.ts
constant POST (line 11) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/lip-sync/route.ts
constant DEFAULT_LIPSYNC_MODEL_KEY (line 13) | const DEFAULT_LIPSYNC_MODEL_KEY = composeModelKey('fal', 'fal-ai/kling-v...
constant POST (line 15) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/location/confirm-selection/route.ts
constant POST (line 18) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/location/route.ts
function toObject (line 7) | function toObject(value: unknown): Record<string, unknown> {
function normalizeString (line 12) | function normalizeString(value: unknown): string {
constant DELETE (line 17) | const DELETE = apiHandler(async (
constant POST (line 43) | const POST = apiHandler(async (
constant PATCH (line 107) | const PATCH = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/modify-asset-image/route.ts
function toNumber (line 16) | function toNumber(value: unknown) {
function toObject (line 21) | function toObject(value: unknown): Record<string, unknown> {
constant POST (line 26) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/modify-storyboard-image/route.ts
function toObject (line 14) | function toObject(value: unknown): Record<string, unknown> {
constant POST (line 19) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/panel-link/route.ts
constant POST (line 7) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/panel-variant/route.ts
function createPanelVariantId (line 11) | function createPanelVariantId(): string {
function rollbackCreatedVariantPanel (line 18) | async function rollbackCreatedVariantPanel(params: {
constant POST (line 69) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/panel/route.ts
function parseNullableNumberField (line 7) | function parseNullableNumberField(value: unknown): number | null {
function toStructuredJsonField (line 17) | function toStructuredJsonField(value: unknown, fieldName: string): strin...
constant POST (line 30) | const POST = apiHandler(async (
constant DELETE (line 115) | const DELETE = apiHandler(async (
constant PATCH (line 213) | const PATCH = apiHandler(async (
constant PUT (line 309) | const PUT = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/panel/select-candidate/route.ts
type PanelHistoryEntry (line 9) | interface PanelHistoryEntry {
function parseUnknownArray (line 14) | function parseUnknownArray(jsonValue: string | null): unknown[] {
function parsePanelHistory (line 24) | function parsePanelHistory(jsonValue: string | null): PanelHistoryEntry[] {
constant POST (line 39) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/photography-plan/route.ts
constant PUT (line 11) | const PUT = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/reference-to-character/route.ts
function parseReferenceImages (line 8) | function parseReferenceImages(body: Record<string, unknown>): string[] {
constant POST (line 20) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/regenerate-group/route.ts
constant POST (line 18) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/regenerate-panel-image/route.ts
constant DEFAULT_CANDIDATE_COUNT (line 14) | const DEFAULT_CANDIDATE_COUNT = 1
constant POST (line 16) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/regenerate-single-image/route.ts
function toNumber (line 15) | function toNumber(value: unknown) {
constant POST (line 20) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/regenerate-storyboard-text/route.ts
constant POST (line 10) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/route.ts
constant MODEL_FIELDS (line 18) | const MODEL_FIELDS = [
constant MODEL_FIELD_TO_TYPE (line 28) | const MODEL_FIELD_TO_TYPE: Record<typeof MODEL_FIELDS[number], UnifiedMo...
constant CAPABILITY_MODEL_TYPES (line 38) | const CAPABILITY_MODEL_TYPES: readonly UnifiedModelType[] = ['image', 'v...
function isRecord (line 40) | function isRecord(value: unknown): value is Record<string, unknown> {
function normalizeCapabilitySelectionsInput (line 44) | function normalizeCapabilitySelectionsInput(
function parseStoredCapabilitySelections (line 87) | function parseStoredCapabilitySelections(raw: string | null | undefined)...
function serializeCapabilitySelections (line 96) | function serializeCapabilitySelections(selections: CapabilitySelections)...
function validateModelKeyField (line 101) | function validateModelKeyField(field: typeof MODEL_FIELDS[number], value...
function validateArtStyleField (line 116) | function validateArtStyleField(value: unknown): string {
function getNextProjectModelMap (line 135) | function getNextProjectModelMap(
function resolveCapabilityContext (line 164) | function resolveCapabilityContext(
function sanitizeCapabilityOverrides (line 180) | function sanitizeCapabilityOverrides(
function validateCapabilityOverrides (line 209) | function validateCapabilityOverrides(
constant GET (line 225) | const GET = apiHandler(async (
constant PATCH (line 266) | const PATCH = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/screenplay-conversion/route.ts
constant POST (line 11) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/script-to-storyboard-stream/route.ts
constant POST (line 9) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/select-character-image/route.ts
constant POST (line 13) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/select-location-image/route.ts
constant POST (line 12) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/speaker-voice/route.ts
function readTrimmedString (line 13) | function readTrimmedString(input: unknown): string | null {
function signUrlIfNeeded (line 19) | function signUrlIfNeeded(url: string): string {
constant GET (line 28) | const GET = apiHandler(async (
constant PATCH (line 81) | const PATCH = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/story-to-script-stream/route.ts
constant POST (line 9) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/storyboard-group/route.ts
constant POST (line 11) | const POST = apiHandler(async (
constant PUT (line 118) | const PUT = apiHandler(async (
constant DELETE (line 199) | const DELETE = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/storyboards/route.ts
constant GET (line 11) | const GET = apiHandler(async (
constant PATCH (line 48) | const PATCH = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/undo-regenerate/route.ts
type CharacterAppearanceRecord (line 15) | interface CharacterAppearanceRecord {
type LocationImageRecord (line 27) | interface LocationImageRecord {
type LocationRecord (line 35) | interface LocationRecord {
type PanelRecord (line 39) | interface PanelRecord {
type UndoRegenerateTx (line 45) | interface UndoRegenerateTx {
type UndoRegenerateDb (line 54) | interface UndoRegenerateDb extends UndoRegenerateTx {
constant POST (line 69) | const POST = apiHandler(async (
function undoCharacterRegenerate (line 109) | async function undoCharacterRegenerate(db: UndoRegenerateDb, appearanceI...
function undoLocationRegenerate (line 166) | async function undoLocationRegenerate(db: UndoRegenerateDb, locationId: ...
function undoPanelRegenerate (line 218) | async function undoPanelRegenerate(db: UndoRegenerateDb, panelId: string) {
FILE: src/app/api/novel-promotion/[projectId]/update-appearance/route.ts
constant POST (line 6) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/update-asset-label/route.ts
constant POST (line 16) | const POST = apiHandler(async (
function updateImageLabel (line 143) | async function updateImageLabel(imageUrl: string, newLabelText: string):...
FILE: src/app/api/novel-promotion/[projectId]/update-location/route.ts
constant POST (line 7) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/update-prompt/route.ts
constant POST (line 6) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/upload-asset-image/route.ts
type CharacterAppearanceRecord (line 10) | interface CharacterAppearanceRecord {
type LocationImageRecord (line 16) | interface LocationImageRecord {
type LocationRecord (line 21) | interface LocationRecord {
type UploadAssetImageDb (line 26) | interface UploadAssetImageDb {
constant POST (line 45) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/video-proxy/route.ts
constant GET (line 11) | const GET = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/video-urls/route.ts
type PanelData (line 6) | interface PanelData {
type StoryboardData (line 13) | interface StoryboardData {
type ClipData (line 19) | interface ClipData {
type EpisodeData (line 23) | interface EpisodeData {
constant POST (line 32) | const POST = apiHandler(async (
type VideoItem (line 100) | interface VideoItem {
type VideoCandidate (line 115) | interface VideoCandidate extends VideoItem {
FILE: src/app/api/novel-promotion/[projectId]/voice-analyze/route.ts
constant POST (line 11) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/voice-design/route.ts
constant POST (line 15) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/voice-generate/route.ts
type VoiceLineRow (line 21) | type VoiceLineRow = {
type CharacterRow (line 27) | type CharacterRow = CharacterVoiceFields & {
type VoiceBindingValidationResult (line 31) | type VoiceBindingValidationResult =
function matchCharacterBySpeaker (line 35) | function matchCharacterBySpeaker(speaker: string, characters: CharacterR...
function validateSpeakerVoiceForProvider (line 40) | function validateSpeakerVoiceForProvider(
function hasSpeakerVoiceForProvider (line 79) | function hasSpeakerVoiceForProvider(
constant POST (line 94) | const POST = apiHandler(async (
FILE: src/app/api/novel-promotion/[projectId]/voice-lines/route.ts
function resolveMatchedPanelData (line 8) | async function resolveMatchedPanelData(
function withVoiceLineMedia (line 52) | async function withVoiceLineMedia<T extends Record<string, unknown>>(lin...
constant GET (line 80) | const GET = apiHandler(async (
constant POST (line 159) | const POST = apiHandler(async (
constant PATCH (line 246) | const PATCH = apiHandler(async (
constant DELETE (line 352) | const DELETE = apiHandler(async (
FILE: src/app/api/projects/[projectId]/assets/route.ts
constant GET (line 11) | const GET = apiHandler(async (
FILE: src/app/api/projects/[projectId]/costs/route.ts
constant GET (line 12) | const GET = apiHandler(async (
FILE: src/app/api/projects/[projectId]/data/route.ts
constant GET (line 12) | const GET = apiHandler(async (
FILE: src/app/api/projects/[projectId]/route.ts
constant GET (line 15) | const GET = apiHandler(async (
constant PATCH (line 55) | const PATCH = apiHandler(async (
function collectProjectCOSKeys (line 100) | async function collectProjectCOSKeys(projectId: string): Promise<string[...
constant DELETE (line 189) | const DELETE = apiHandler(async (
FILE: src/app/api/projects/route.ts
constant GET (line 9) | const GET = apiHandler(async (request: NextRequest) => {
constant POST (line 166) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/runs/[runId]/cancel/route.ts
constant POST (line 9) | const POST = apiHandler(async (
FILE: src/app/api/runs/[runId]/events/route.ts
constant GET (line 6) | const GET = apiHandler(async (
FILE: src/app/api/runs/[runId]/route.ts
constant GET (line 6) | const GET = apiHandler(async (
FILE: src/app/api/runs/[runId]/steps/[stepKey]/retry/route.ts
constant RETRY_SUPPORTED_TASK_TYPES (line 9) | const RETRY_SUPPORTED_TASK_TYPES: ReadonlySet<string> = new Set<string>([
function toObject (line 14) | function toObject(value: unknown): Record<string, unknown> {
function readString (line 19) | function readString(value: unknown): string {
function resolveTaskType (line 23) | function resolveTaskType(run: {
constant POST (line 37) | const POST = apiHandler(async (
FILE: src/app/api/runs/route.ts
function readString (line 7) | function readString(value: unknown): string | null {
function normalizeStatus (line 13) | function normalizeStatus(value: string | null): RunStatus | null {
function normalizeStatuses (line 26) | function normalizeStatuses(values: string[]): RunStatus[] {
constant GET (line 38) | const GET = apiHandler(async (request: NextRequest) => {
constant POST (line 64) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/sse/route.ts
function parseReplayCursorId (line 11) | function parseReplayCursorId(value: string | null): number {
function formatSSE (line 19) | function formatSSE(event: SSEEvent) {
function formatHeartbeat (line 27) | function formatHeartbeat() {
function asObject (line 31) | function asObject(value: unknown): Record<string, unknown> | null {
function listActiveLifecycleSnapshot (line 36) | async function listActiveLifecycleSnapshot(params: {
constant GET (line 92) | const GET = apiHandler(async (request: NextRequest) => {
method start (line 114) | async start(controller) {
method cancel (line 196) | cancel() {
FILE: src/app/api/storage/sign/route.ts
constant DEFAULT_EXPIRES_SECONDS (line 5) | const DEFAULT_EXPIRES_SECONDS = 3600
constant GET (line 7) | const GET = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/system/boot-id/route.ts
function GET (line 8) | async function GET() {
FILE: src/app/api/task-target-states/route.ts
function normalizeTarget (line 10) | function normalizeTarget(input: unknown): TaskTargetQuery {
constant POST (line 28) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/tasks/[taskId]/route.ts
function toObject (line 10) | function toObject(value: unknown): Record<string, unknown> {
constant GET (line 15) | const GET = apiHandler(async (
constant DELETE (line 42) | const DELETE = apiHandler(async (
FILE: src/app/api/tasks/dismiss/route.ts
constant POST (line 6) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/tasks/route.ts
function withTaskError (line 8) | function withTaskError(task: Awaited<ReturnType<typeof queryTasks>>[numb...
constant GET (line 16) | const GET = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/user-preference/route.ts
function validateArtStyleField (line 7) | function validateArtStyleField(value: unknown): string {
constant GET (line 27) | const GET = apiHandler(async () => {
constant PATCH (line 44) | const PATCH = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/user/api-config/assistant/probe-media-template/route.ts
type RequestBody (line 8) | type RequestBody = {
function readRequiredString (line 16) | function readRequiredString(value: unknown, field: string): string {
constant POST (line 26) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/user/api-config/assistant/validate-media-template/route.ts
type RequestBody (line 7) | type RequestBody = {
function readRequiredString (line 12) | function readRequiredString(value: unknown, field: string): string {
constant POST (line 22) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/user/api-config/probe-model-llm-protocol/route.ts
type ProbeRequestBody (line 7) | type ProbeRequestBody = {
function readRequiredString (line 12) | function readRequiredString(value: unknown, field: string): string {
constant POST (line 22) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/user/api-config/route.ts
type ApiModeType (line 45) | type ApiModeType = 'gemini-sdk' | 'openai-official'
type GatewayRouteType (line 46) | type GatewayRouteType = 'official' | 'openai-compat'
type LlmProtocolType (line 47) | type LlmProtocolType = 'responses' | 'chat-completions'
type DefaultModelField (line 48) | type DefaultModelField =
type StoredProvider (line 59) | interface StoredProvider {
type StoredModelLlmCustomPricing (line 69) | interface StoredModelLlmCustomPricing {
type StoredModelMediaCustomPricing (line 74) | interface StoredModelMediaCustomPricing {
type StoredModelCustomPricing (line 79) | interface StoredModelCustomPricing {
type StoredModel (line 85) | interface StoredModel {
type PricingDisplayItem (line 107) | interface PricingDisplayItem {
type PricingDisplayMap (line 115) | type PricingDisplayMap = Record<string, PricingDisplayItem>
type DefaultModelsPayload (line 117) | interface DefaultModelsPayload {
type WorkflowConcurrencyPayload (line 129) | interface WorkflowConcurrencyPayload {
type ApiConfigPutBody (line 135) | interface ApiConfigPutBody {
constant DEFAULT_MODEL_FIELDS (line 143) | const DEFAULT_MODEL_FIELDS: DefaultModelField[] = [
constant CAPABILITY_MODEL_TYPES (line 154) | const CAPABILITY_MODEL_TYPES: readonly UnifiedModelType[] = [
constant BILLABLE_MODEL_TYPE_TO_PRICING_API_TYPE (line 161) | const BILLABLE_MODEL_TYPE_TO_PRICING_API_TYPE: Readonly<Record<UnifiedMo...
constant DEFAULT_FIELD_TO_PRICING_API_TYPE (line 168) | const DEFAULT_FIELD_TO_PRICING_API_TYPE: Readonly<Record<DefaultModelFie...
constant DEFAULT_LIPSYNC_MODEL_KEY (line 179) | const DEFAULT_LIPSYNC_MODEL_KEY = composeModelKey('fal', 'fal-ai/kling-v...
constant PRICING_PROVIDER_ALIASES (line 185) | const PRICING_PROVIDER_ALIASES: Readonly<Record<string, string>> = {
constant OPTIONAL_PRICING_PROVIDER_KEYS (line 188) | const OPTIONAL_PRICING_PROVIDER_KEYS = new Set([
constant OFFICIAL_ONLY_PROVIDER_KEYS (line 194) | const OFFICIAL_ONLY_PROVIDER_KEYS = new Set(['bailian', 'siliconflow'])
constant RETIRED_PROVIDER_KEYS (line 195) | const RETIRED_PROVIDER_KEYS = new Set(['qwen'])
constant MINIMAX_OFFICIAL_BASE_URL (line 196) | const MINIMAX_OFFICIAL_BASE_URL = 'https://api.minimaxi.com/v1'
function isRecord (line 198) | function isRecord(value: unknown): value is Record<string, unknown> {
function readTrimmedString (line 202) | function readTrimmedString(value: unknown): string {
function normalizeMinimaxProviderBaseUrl (line 206) | function normalizeMinimaxProviderBaseUrl(input: {
function formatPriceAmount (line 224) | function formatPriceAmount(amount: number): string {
function pricingApiTypeToModelType (line 230) | function pricingApiTypeToModelType(apiType: PricingApiType): UnifiedMode...
function composePricingDisplayKey (line 239) | function composePricingDisplayKey(modelType: UnifiedModelType, provider:...
function resolveVideoDurationRangeFromCapabilities (line 243) | function resolveVideoDurationRangeFromCapabilities(
function applyVideoDurationRangeIfNeeded (line 259) | function applyVideoDurationRangeIfNeeded(input: {
function buildPricingDisplayMap (line 287) | function buildPricingDisplayMap(): PricingDisplayMap {
function resolvePricingDisplayItem (line 343) | function resolvePricingDisplayItem(
function withDisplayPricing (line 367) | function withDisplayPricing(model: StoredModel, map: PricingDisplayMap):...
function getProviderKey (line 437) | function getProviderKey(providerId: string): string {
function isUnifiedModelType (line 442) | function isUnifiedModelType(value: unknown): value is UnifiedModelType {
function isApiMode (line 452) | function isApiMode(value: unknown): value is ApiModeType {
function isGatewayRoute (line 456) | function isGatewayRoute(value: unknown): value is GatewayRouteType {
function isLlmProtocol (line 460) | function isLlmProtocol(value: unknown): value is LlmProtocolType {
function isMediaTemplateSource (line 464) | function isMediaTemplateSource(value: unknown): value is OpenAICompatMed...
function resolveProviderGatewayRoute (line 468) | function resolveProviderGatewayRoute(
function resolveProviderByIdOrKey (line 512) | function resolveProviderByIdOrKey(providers: StoredProvider[], providerI...
function withBuiltinCapabilities (line 529) | function withBuiltinCapabilities(model: StoredModel): StoredModel {
function readNonNegativeNumber (line 544) | function readNonNegativeNumber(value: unknown): number | undefined {
function parseNonNegativeNumberStrict (line 551) | function parseNonNegativeNumberStrict(value: unknown, field: string): nu...
function validateAllowedObjectKeys (line 561) | function validateAllowedObjectKeys(
function normalizeOptionPrices (line 576) | function normalizeOptionPrices(
function normalizeMediaCustomPricing (line 623) | function normalizeMediaCustomPricing(
function normalizeCustomPricing (line 655) | function normalizeCustomPricing(
function normalizeStoredModel (line 735) | function normalizeStoredModel(raw: unknown, index: number, options?: { s...
function normalizeProvidersInput (line 834) | function normalizeProvidersInput(rawProviders: unknown): StoredProvider[] {
function normalizeModelList (line 928) | function normalizeModelList(rawModels: unknown): StoredModel[] {
function validateModelProviderConsistency (line 940) | function validateModelProviderConsistency(models: StoredModel[], provide...
function validateModelProviderTypeSupport (line 953) | function validateModelProviderTypeSupport(models: StoredModel[], provide...
function isOpenAICompatibleLlmModel (line 969) | function isOpenAICompatibleLlmModel(model: StoredModel): boolean {
function isOpenAICompatibleMediaTemplateModel (line 973) | function isOpenAICompatibleMediaTemplateModel(model: StoredModel): boole...
function getDefaultMediaTemplate (line 978) | function getDefaultMediaTemplate(type: 'image' | 'video'): OpenAICompatM...
function resolveStoredLlmProtocols (line 1040) | function resolveStoredLlmProtocols(
function resolveStoredMediaTemplates (line 1090) | function resolveStoredMediaTemplates(
function validateCustomPricingCapabilityMappings (line 1144) | function validateCustomPricingCapabilityMappings(models: StoredModel[]) {
function hasBuiltinPricingForModel (line 1186) | function hasBuiltinPricingForModel(apiType: PricingApiType, provider: st...
function hasCustomPricingForType (line 1191) | function hasCustomPricingForType(model: StoredModel): boolean {
function validateBillableModelPricing (line 1216) | function validateBillableModelPricing(models: StoredModel[]) {
function validateDefaultModelKey (line 1237) | function validateDefaultModelKey(field: DefaultModelField, value: unknow...
function normalizeDefaultModelsInput (line 1252) | function normalizeDefaultModelsInput(rawDefaultModels: unknown): Default...
function normalizeWorkflowConcurrencyInput (line 1271) | function normalizeWorkflowConcurrencyInput(rawWorkflowConcurrency: unkno...
function validateDefaultModelPricing (line 1327) | function validateDefaultModelPricing(defaultModels: DefaultModelsPayload) {
function isModelPricedForBilling (line 1348) | function isModelPricedForBilling(model: StoredModel): boolean {
function sanitizeModelsForBilling (line 1356) | function sanitizeModelsForBilling(models: StoredModel[]): StoredModel[] {
function sanitizeDefaultModelsForBilling (line 1360) | function sanitizeDefaultModelsForBilling(defaultModels: DefaultModelsPay...
function parseStoredProviders (line 1391) | function parseStoredProviders(rawProviders: string | null | undefined): ...
function parseStoredModels (line 1485) | function parseStoredModels(rawModels: string | null | undefined): Stored...
function normalizeCapabilitySelectionsInput (line 1509) | function normalizeCapabilitySelectionsInput(
function parseStoredCapabilitySelections (line 1556) | function parseStoredCapabilitySelections(raw: string | null | undefined,...
function serializeCapabilitySelections (line 1572) | function serializeCapabilitySelections(selections: CapabilitySelections)...
function buildStoredModelMap (line 1577) | function buildStoredModelMap(models: StoredModel[]): Map<string, StoredM...
function resolveCapabilityContextForModelKey (line 1585) | function resolveCapabilityContextForModelKey(
function sanitizeCapabilitySelectionsAgainstModels (line 1602) | function sanitizeCapabilitySelectionsAgainstModels(
function validateCapabilitySelectionsAgainstModels (line 1632) | function validateCapabilitySelectionsAgainstModels(
constant GET (line 1652) | const GET = apiHandler(async () => {
constant PUT (line 1764) | const PUT = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/user/api-config/test-connection/route.ts
constant POST (line 6) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/user/api-config/test-provider/route.ts
constant POST (line 6) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/user/assistant/chat/route.ts
type RequestBody (line 10) | type RequestBody = {
function readAssistantId (line 16) | function readAssistantId(value: unknown): 'api-config-template' | 'tutor...
function mapAssistantError (line 27) | function mapAssistantError(error: AssistantPlatformError): ApiError {
constant POST (line 55) | const POST = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/user/balance/route.ts
constant GET (line 11) | const GET = apiHandler(async () => {
FILE: src/app/api/user/costs/details/route.ts
constant GET (line 11) | const GET = apiHandler(async (request: NextRequest) => {
FILE: src/app/api/user/costs/route.ts
constant GET (line 12) | const GET = apiHandler(async () => {
FILE: src/app/api/user/models/route.ts
type StoredModelType (line 23) | type StoredModelType = UnifiedModelType | string
type StoredModel (line 25) | interface StoredModel {
type StoredProvider (line 33) | interface StoredProvider {
type UserModelOption (line 39) | interface UserModelOption {
type UserModelsPayload (line 48) | interface UserModelsPayload {
constant AUDIO_MODEL_EXCLUDED_IDS (line 56) | const AUDIO_MODEL_EXCLUDED_IDS = new Set([
function isUnifiedModelType (line 60) | function isUnifiedModelType(type: unknown): type is UnifiedModelType {
function toModelKey (line 70) | function toModelKey(model: StoredModel): string {
function toProvider (line 82) | function toProvider(model: StoredModel): string | undefined {
function toModelId (line 88) | function toModelId(model: StoredModel): string {
function toDisplayLabel (line 96) | function toDisplayLabel(model: StoredModel, fallbackModelId: string): st...
function dedupeByModelKey (line 101) | function dedupeByModelKey(items: UserModelOption[]): UserModelOption[] {
function cloneVideoPricingTiers (line 110) | function cloneVideoPricingTiers(rawTiers: Array<{ when: Record<string, C...
function parseStoredModels (line 116) | function parseStoredModels(rawModels: string | null | undefined): Stored...
function parseStoredProviders (line 136) | function parseStoredProviders(rawProviders: string | null | undefined): ...
function hasStoredProviderApiKey (line 156) | function hasStoredProviderApiKey(provider: StoredProvider): boolean {
function isUserSelectableModel (line 160) | function isUserSelectableModel(model: StoredModel): boolean {
constant GET (line 166) | const GET = apiHandler(async () => {
FILE: src/app/api/user/transactions/route.ts
constant ACTION_KEY_PATTERN (line 10) | const ACTION_KEY_PATTERN = /^[a-z][a-z0-9_]*$/
function extractActionFromDescription (line 19) | function extractActionFromDescription(description: string | null): strin...
constant GET (line 31) | const GET = apiHandler(async (request: NextRequest) => {
FILE: src/app/m/[publicId]/route.ts
function buildEtag (line 7) | function buildEtag(media: { sha256?: string | null; id: string; updatedA...
function GET (line 12) | async function GET(
function HEAD (line 74) | async function HEAD(
FILE: src/components/ConfirmDialog.tsx
type ConfirmDialogProps (line 6) | interface ConfirmDialogProps {
function ConfirmDialog (line 17) | function ConfirmDialog({
FILE: src/components/LanguageSwitcher.tsx
constant LANGUAGE_LABELS (line 10) | const LANGUAGE_LABELS: Record<Locale, string> = {
constant SWITCH_CONFIRM_COPY (line 15) | const SWITCH_CONFIRM_COPY: Record<Locale, { title: string; message: stri...
function isSupportedLocale (line 34) | function isSupportedLocale(locale?: string): locale is Locale {
function LanguageSwitcher (line 38) | function LanguageSwitcher() {
FILE: src/components/Navbar.tsx
function Navbar (line 14) | function Navbar() {
FILE: src/components/ProgressToast.tsx
type ProgressToastRunBadge (line 5) | interface ProgressToastRunBadge {
type ProgressToastProps (line 11) | interface ProgressToastProps {
function ProgressToast (line 18) | function ProgressToast({ show, message, step, runBadges }: ProgressToast...
FILE: src/components/UpdateNoticeModal.tsx
type UpdateNoticeModalProps (line 8) | interface UpdateNoticeModalProps {
function UpdateNoticeModal (line 18) | function UpdateNoticeModal({
FILE: src/components/ai-elements/conversation.tsx
constant BOTTOM_THRESHOLD_PX (line 14) | const BOTTOM_THRESHOLD_PX = 24;
type ConversationContextValue (line 16) | interface ConversationContextValue {
function useConversationContext (line 25) | function useConversationContext(): ConversationContextValue {
function joinClassName (line 33) | function joinClassName(...values: Array<string | undefined>): string {
function computeIsAtBottom (line 37) | function computeIsAtBottom(element: HTMLDivElement): boolean {
type ConversationProps (line 42) | type ConversationProps = HTMLAttributes<HTMLDivElement>;
type ConversationContentProps (line 89) | type ConversationContentProps = HTMLAttributes<HTMLDivElement>;
type ConversationScrollButtonProps (line 125) | type ConversationScrollButtonProps = HTMLAttributes<HTMLButtonElement>;
FILE: src/components/ai-elements/message.tsx
type MessageProps (line 6) | type MessageProps = HTMLAttributes<HTMLDivElement> & {
function joinClassName (line 10) | function joinClassName(...values: Array<string | undefined>): string {
type MessageContentProps (line 25) | type MessageContentProps = HTMLAttributes<HTMLDivElement>;
type MessageResponseProps (line 43) | type MessageResponseProps = HTMLAttributes<HTMLDivElement>;
FILE: src/components/ai-elements/reasoning.tsx
type ReasoningContextValue (line 6) | interface ReasoningContextValue {
function useReasoningContext (line 15) | function useReasoningContext(): ReasoningContextValue {
type ReasoningProps (line 23) | type ReasoningProps = HTMLAttributes<HTMLDivElement> & {
function joinClassName (line 31) | function joinClassName(...values: Array<string | undefined>): string {
type ReasoningTriggerProps (line 71) | type ReasoningTriggerProps = HTMLAttributes<HTMLButtonElement> & {
type ReasoningContentProps (line 105) | type ReasoningContentProps = HTMLAttributes<HTMLDivElement> & {
FILE: src/components/ai-elements/tool.tsx
type ToolPart (line 7) | type ToolPart = ToolUIPart | DynamicToolUIPart;
type ToolContextValue (line 9) | interface ToolContextValue {
function useToolContext (line 16) | function useToolContext(): ToolContextValue {
function joinClassName (line 22) | function joinClassName(...values: Array<string | undefined>): string {
type ToolProps (line 26) | type ToolProps = HTMLAttributes<HTMLDivElement> & {
type ToolHeaderProps (line 47) | type ToolHeaderProps = HTMLAttributes<HTMLButtonElement> & {
type ToolContentProps (line 87) | type ToolContentProps = HTMLAttributes<HTMLDivElement>;
function formatJson (line 103) | function formatJson(value: unknown): string {
type ToolInputProps (line 111) | type ToolInputProps = HTMLAttributes<HTMLDivElement> & {
type ToolOutputProps (line 124) | type ToolOutputProps = HTMLAttributes<HTMLDivElement> & {
FILE: src/components/assistant/AssistantChatModal.tsx
type AssistantChatModalProps (line 11) | interface AssistantChatModalProps {
type ParsedMessageContent (line 37) | interface ParsedMessageContent {
type ParsedToolPart (line 42) | type ParsedToolPart =
type RenderableMessage (line 59) | interface RenderableMessage {
type MessageCacheEntry (line 67) | interface MessageCacheEntry {
function isRecord (line 72) | function isRecord(value: unknown): value is Record<string, unknown> {
function readTrimmedString (line 76) | function readTrimmedString(value: unknown): string {
function joinClassNames (line 80) | function joinClassNames(...values: Array<string | undefined>): string {
function isToolState (line 84) | function isToolState(value: string): value is ToolPart['state'] {
function isToolPartType (line 94) | function isToolPartType(value: string): value is ToolPart['type'] {
function splitThinkTaggedContent (line 99) | function splitThinkTaggedContent(input: string): { text: string; reasoni...
function parseToolPart (line 139) | function parseToolPart(part: unknown): ParsedToolPart | null {
function buildMessageSignature (line 169) | function buildMessageSignature(message: UIMessage): string {
function extractMessageContent (line 191) | function extractMessageContent(message: UIMessage): ParsedMessageContent {
function buildRenderableMessage (line 248) | function buildRenderableMessage(message: UIMessage): RenderableMessage {
function onEnterSubmit (line 263) | function onEnterSubmit(event: KeyboardEvent<HTMLInputElement>, submit: (...
function AssistantChatModal (line 270) | function AssistantChatModal({
FILE: src/components/assistant/useAssistantChat.ts
type AssistantChatId (line 8) | type AssistantChatId = 'api-config-template' | 'tutorial'
type AssistantDraftModel (line 10) | interface AssistantDraftModel {
type AssistantSavedEvent (line 18) | interface AssistantSavedEvent {
type UseAssistantChatParams (line 23) | interface UseAssistantChatParams {
function isRecord (line 33) | function isRecord(value: unknown): value is Record<string, unknown> {
function readTrimmedString (line 37) | function readTrimmedString(value: unknown): string {
function readNonEmptyStringArray (line 41) | function readNonEmptyStringArray(value: unknown): string[] {
function parseDraftModel (line 48) | function parseDraftModel(value: unknown): AssistantDraftModel | undefined {
function readSavedEventsFromPart (line 66) | function readSavedEventsFromPart(part: unknown): AssistantSavedEvent[] {
function collectSavedEvents (line 107) | function collectSavedEvents(messages: UIMessage[]): AssistantSavedEvent[] {
type UseAssistantChatResult (line 117) | interface UseAssistantChatResult {
function useAssistantChat (line 128) | function useAssistantChat(params: UseAssistantChatParams): UseAssistantC...
FILE: src/components/auth/PasswordStrengthIndicator.tsx
type PasswordStrengthIndicatorProps (line 6) | interface PasswordStrengthIndicatorProps {
type StrengthLevel (line 10) | type StrengthLevel = 'weak' | 'fair' | 'good' | 'strong'
type StrengthResult (line 12) | interface StrengthResult {
function evaluateStrength (line 32) | function evaluateStrength(password: string): StrengthResult {
constant LEVEL_STYLES (line 74) | const LEVEL_STYLES: Record<StrengthLevel, { color: string; bgActive: str...
function PasswordStrengthIndicator (line 93) | function PasswordStrengthIndicator({ password }: PasswordStrengthIndicat...
FILE: src/components/image-generation/ImageGenerationInlineCountButton.tsx
type ImageGenerationInlineCountButtonProps (line 6) | interface ImageGenerationInlineCountButtonProps {
function ImageGenerationInlineCountButton (line 22) | function ImageGenerationInlineCountButton({
FILE: src/components/image-generation/ImageGenerationSlotOverlay.tsx
type ImageGenerationSlotOverlayProps (line 5) | interface ImageGenerationSlotOverlayProps {
function ImageGenerationSlotOverlay (line 9) | function ImageGenerationSlotOverlay({ label }: ImageGenerationSlotOverla...
FILE: src/components/llm-console/LLMStageStreamCard.tsx
type LLMStageViewStatus (line 6) | type LLMStageViewStatus =
type LLMStageViewItem (line 15) | type LLMStageViewItem = {
type LLMStageStreamCardProps (line 25) | type LLMStageStreamCardProps = {
constant PROGRESS_KEY_PREFIX (line 44) | const PROGRESS_KEY_PREFIX = 'progress.'
constant REASONING_HEADER (line 45) | const REASONING_HEADER = '【思考过程】'
constant FINAL_HEADER (line 46) | const FINAL_HEADER = '【最终结果】'
function statusClass (line 48) | function statusClass(status: LLMStageViewStatus): string {
function clampProgress (line 58) | function clampProgress(value: number): number {
function splitThinkTaggedContent (line 63) | function splitThinkTaggedContent(input: string): { text: string; reasoni...
function mergeReasoning (line 101) | function mergeReasoning(base: string, incoming: string): string {
function splitStructuredOutput (line 111) | function splitStructuredOutput(raw: string): {
function LLMStageStreamCard (line 168) | function LLMStageStreamCard({
FILE: src/components/media/MediaImage.tsx
type MediaImageProps (line 6) | type MediaImageProps = {
function isStableMediaRoute (line 19) | function isStableMediaRoute(src: string) {
function MediaImage (line 23) | function MediaImage({
FILE: src/components/media/MediaImageWithLoading.tsx
type MediaImageWithLoadingProps (line 6) | type MediaImageWithLoadingProps = MediaImageProps & {
function mergeClassNames (line 14) | function mergeClassNames(...classNames: Array<string | undefined | false...
function MediaImageWithLoading (line 18) | function MediaImageWithLoading({
FILE: src/components/providers/QueryProvider.tsx
function QueryProvider (line 10) | function QueryProvider({ children }: { children: React.ReactNode }) {
FILE: src/components/shared/assets/CharacterCreationModal.tsx
type CharacterCreationModalProps (line 13) | interface CharacterCreationModalProps {
function CharacterCreationModal (line 25) | function CharacterCreationModal({
FILE: src/components/shared/assets/CharacterEditModal.tsx
type CharacterEditModalProps (line 19) | interface CharacterEditModalProps {
function CharacterEditModal (line 39) | function CharacterEditModal({
FILE: src/components/shared/assets/GlobalAssetPicker.tsx
type GlobalAssetPickerProps (line 12) | interface GlobalAssetPickerProps {
type GlobalCharacterAppearance (line 20) | interface GlobalCharacterAppearance {
type GlobalCharacter (line 27) | interface GlobalCharacter {
type GlobalLocationImage (line 35) | interface GlobalLocationImage {
type GlobalLocation (line 42) | interface GlobalLocation {
type GlobalVoice (line 50) | interface GlobalVoice {
function getCharacterPreview (line 64) | function getCharacterPreview(char: GlobalCharacter): string | null {
function getLocationPreview (line 75) | function getLocationPreview(loc: GlobalLocation): string | null {
function GlobalAssetPicker (line 107) | function GlobalAssetPicker({
FILE: src/components/shared/assets/LocationCreationModal.tsx
type LocationCreationModalProps (line 23) | interface LocationCreationModalProps {
function LocationCreationModal (line 42) | function LocationCreationModal({
FILE: src/components/shared/assets/LocationEditModal.tsx
type LocationEditModalProps (line 18) | interface LocationEditModalProps {
function LocationEditModal (line 35) | function LocationEditModal({
FILE: src/components/shared/assets/character-creation/CharacterCreationForm.tsx
type Mode (line 10) | type Mode = 'asset-hub' | 'project'
type AvailableCharacter (line 12) | interface AvailableCharacter {
type CharacterCreationFormProps (line 18) | interface CharacterCreationFormProps {
function CharacterCreationForm (line 59) | function CharacterCreationForm({
FILE: src/components/shared/assets/character-creation/CharacterCreationPreview.tsx
type CharacterCreationPreviewProps (line 8) | interface CharacterCreationPreviewProps {
function CharacterCreationPreview (line 20) | function CharacterCreationPreview({
FILE: src/components/shared/assets/character-creation/hooks/useCharacterCreationSubmit.ts
type Mode (line 21) | type Mode = 'asset-hub' | 'project'
type UseCharacterCreationSubmitParams (line 23) | interface UseCharacterCreationSubmitParams {
function useCharacterCreationSubmit (line 47) | function useCharacterCreationSubmit({
FILE: src/components/task/TaskStatusInline.tsx
type TaskStatusInlineProps (line 7) | type TaskStatusInlineProps = {
function TaskStatusInline (line 12) | function TaskStatusInline({ state, className }: TaskStatusInlineProps) {
FILE: src/components/task/TaskStatusOverlay.tsx
type TaskStatusOverlayProps (line 7) | type TaskStatusOverlayProps = {
function TaskStatusOverlay (line 12) | function TaskStatusOverlay({ state, className }: TaskStatusOverlayProps) {
FILE: src/components/ui/CapsuleNav.tsx
type StepStatus (line 7) | type StepStatus = 'empty' | 'active' | 'processing' | 'ready'
type NavItemData (line 9) | interface NavItemData {
type CapsuleNavProps (line 19) | interface CapsuleNavProps {
function NavItem (line 31) | function NavItem({
function CapsuleNav (line 123) | function CapsuleNav({ items, activeId, onItemClick, projectId, episodeId...
type Episode (line 167) | interface Episode {
type EpisodeSelectorProps (line 178) | interface EpisodeSelectorProps {
function EpisodeSelector (line 188) | function EpisodeSelector({
FILE: src/components/ui/ImagePreviewModal.tsx
type ImagePreviewModalProps (line 9) | interface ImagePreviewModalProps {
function ImagePreviewModal (line 14) | function ImagePreviewModal({ imageUrl, onClose }: ImagePreviewModalProps) {
FILE: src/components/ui/SegmentedControl.tsx
type SegmentedControlOption (line 7) | interface SegmentedControlOption<T extends string = string> {
type SegmentedControlProps (line 12) | interface SegmentedControlProps<T extends string = string> {
function SegmentedControl (line 29) | function SegmentedControl<T extends string = string>({
FILE: src/components/ui/SharedComponents.tsx
function AnimatedBackground (line 7) | function AnimatedBackground() {
function GlassPanel (line 23) | function GlassPanel({
function Button (line 43) | function Button({
FILE: src/components/ui/ai-edit-style.ts
constant AI_EDIT_BUTTON_CLASS (line 1) | const AI_EDIT_BUTTON_CLASS = 'bg-[var(--glass-bg-surface-strong)] border...
constant AI_EDIT_ICON_CLASS (line 3) | const AI_EDIT_ICON_CLASS = ''
FILE: src/components/ui/config-modals/ConfigConfirmModal.tsx
type ConfigConfirmModalProps (line 5) | interface ConfigConfirmModalProps {
function ConfigConfirmModal (line 17) | function ConfigConfirmModal({
FILE: src/components/ui/config-modals/ConfigDeleteModal.tsx
type ConfigDeleteModalProps (line 6) | interface ConfigDeleteModalProps {
function ConfigDeleteModal (line 17) | function ConfigDeleteModal({
FILE: src/components/ui/config-modals/ConfigEditModal.tsx
type ModelOption (line 19) | interface ModelOption {
type UserModels (line 27) | interface UserModels {
type CapabilityFieldDefinition (line 34) | interface CapabilityFieldDefinition {
type SettingsModalProps (line 40) | interface SettingsModalProps {
function isRecord (line 71) | function isRecord(value: unknown): value is Record<string, unknown> {
function isCapabilityValue (line 75) | function isCapabilityValue(value: unknown): value is CapabilityValue {
function toFieldLabel (line 79) | function toFieldLabel(field: string): string {
function parseBySample (line 83) | function parseBySample(input: string, sample: CapabilityValue): Capabili...
function extractCapabilityFields (line 89) | function extractCapabilityFields(
function readCapabilitySelectionForModel (line 108) | function readCapabilitySelectionForModel(
function SettingsModal (line 125) | function SettingsModal({
FILE: src/components/ui/config-modals/ModelCapabilityDropdown.tsx
type ModelCapabilityOption (line 21) | interface ModelCapabilityOption {
type CapabilityFieldDefinition (line 34) | interface CapabilityFieldDefinition {
type CapabilityBooleanToggle (line 41) | interface CapabilityBooleanToggle {
type ModelCapabilityDropdownProps (line 50) | interface ModelCapabilityDropdownProps {
constant DEFAULT_PANEL_MAX_HEIGHT (line 73) | const DEFAULT_PANEL_MAX_HEIGHT = 520
constant VIEWPORT_EDGE_GAP (line 74) | const VIEWPORT_EDGE_GAP = 16
function RatioIcon (line 78) | function RatioIcon({ ratio, size = 12, selected = false }: { ratio: stri...
function isRatioLike (line 89) | function isRatioLike(field: string, options: CapabilityValue[]): boolean {
function isValidRatioText (line 95) | function isValidRatioText(value: string): boolean {
function shouldUseSelectControl (line 99) | function shouldUseSelectControl(field: string, options: CapabilityValue[...
function isOptionDisabled (line 107) | function isOptionDisabled(def: CapabilityFieldDefinition, option: Capabi...
function ModelCapabilityDropdown (line 114) | function ModelCapabilityDropdown({
FILE: src/components/ui/config-modals/WorldContextModal.tsx
type WorldContextModalProps (line 7) | interface WorldContextModalProps {
function WorldContextModal (line 14) | function WorldContextModal({ isOpen, onClose, text, onChange }: WorldCon...
FILE: src/components/ui/config-modals/config-modal-selectors.tsx
type RatioIconProps (line 6) | interface RatioIconProps {
type RatioSelectorProps (line 12) | interface RatioSelectorProps {
type StyleSelectorProps (line 18) | interface StyleSelectorProps {
function RatioIcon (line 24) | function RatioIcon({ ratio, size = 24, selected = false }: RatioIconProp...
function RatioSelector (line 36) | function RatioSelector({ value, onChange, options }: RatioSelectorProps) {
function StyleSelector (line 107) | function StyleSelector({ value, onChange, options }: StyleSelectorProps) {
FILE: src/components/ui/icons/AISparklesIcon.tsx
type AISparklesIconProps (line 4) | interface AISparklesIconProps {
function AISparklesIcon (line 8) | function AISparklesIcon({ className }: AISparklesIconProps) {
FILE: src/components/ui/icons/AppIcon.tsx
type AppIconProps (line 4) | interface AppIconProps extends Omit<LucideProps, 'ref'> {
function AppIcon (line 8) | function AppIcon({ name, ...props }: AppIconProps) {
FILE: src/components/ui/icons/RatioPreviewIcon.tsx
type RatioPreviewVariant (line 3) | type RatioPreviewVariant = 'surface' | 'surfaceStrong'
type RatioPreviewIconProps (line 5) | interface RatioPreviewIconProps {
function resolveUnselectedClass (line 13) | function resolveUnselectedClass(variant: RatioPreviewVariant): string {
function RatioPreviewIcon (line 20) | function RatioPreviewIcon({
FILE: src/components/ui/icons/custom.tsx
type CustomIconComponent (line 4) | type CustomIconComponent = ForwardRefExoticComponent<Omit<LucideProps, '...
function createLucideIcon (line 6) | function createLucideIcon(name: string, iconNode: IconNode) {
FILE: src/components/ui/icons/registry.ts
type AppIconName (line 191) | type AppIconName = keyof typeof iconRegistry
FILE: src/components/ui/model-dropdown-innovative.tsx
type ModelDropdownTestProps (line 8) | interface ModelDropdownTestProps {
constant VIEWPORT_EDGE_GAP (line 18) | const VIEWPORT_EDGE_GAP = 8
constant DEFAULT_MAX_HEIGHT (line 19) | const DEFAULT_MAX_HEIGHT = 400
function useDropdown (line 21) | function useDropdown(isOpen: boolean, setIsOpen: (val: boolean) => void,...
function resolveParamSummary (line 86) | function resolveParamSummary(fields: CapabilityFieldDefinition[], overri...
function ModelInnovativeV6 (line 99) | function ModelInnovativeV6(props: ModelDropdownTestProps) {
function ModelInnovativeV7 (line 195) | function ModelInnovativeV7(props: ModelDropdownTestProps) {
function ModelInnovativeV8 (line 282) | function ModelInnovativeV8(props: ModelDropdownTestProps) {
function ModelInnovativeV9 (line 386) | function ModelInnovativeV9(props: ModelDropdownTestProps) {
function ModelInnovativeV10 (line 494) | function ModelInnovativeV10(props: ModelDropdownTestProps) {
FILE: src/components/ui/model-dropdown-ios.tsx
type ModelDropdownTestProps (line 9) | interface ModelDropdownTestProps {
constant VIEWPORT_EDGE_GAP (line 19) | const VIEWPORT_EDGE_GAP = 8
constant DEFAULT_MAX_HEIGHT (line 20) | const DEFAULT_MAX_HEIGHT = 450
function useDropdown (line 22) | function useDropdown(isOpen: boolean, setIsOpen: (val: boolean) => void) {
function resolveParamSummary (line 81) | function resolveParamSummary(fields: CapabilityFieldDefinition[], overri...
function DefaultParamsRenderer (line 88) | function DefaultParamsRenderer({ fields, overrides, onChange, className ...
function IOSVariant1 (line 137) | function IOSVariant1(props: ModelDropdownTestProps) {
function IOSVariant2 (line 181) | function IOSVariant2(props: ModelDropdownTestProps) {
function IOSVariant3 (line 226) | function IOSVariant3(props: ModelDropdownTestProps) {
function IOSVariant4 (line 275) | function IOSVariant4(props: ModelDropdownTestProp
Condensed preview — 1330 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (6,872K chars).
[
{
"path": ".dockerignore",
"chars": 443,
"preview": "# 版本控制\n.git\n.github\n\n# 构建产物(builder 阶段重新生成)\n.next\nnode_modules\n\n# 测试\ncoverage\ntests\nvitest.config.ts\ndocker-compose.test"
},
{
"path": ".eslintrc.json",
"chars": 240,
"preview": "{\n \"extends\": \"next/core-web-vitals\",\n \"rules\": {\n \"@typescript-eslint/no-explicit-any\": \"off\",\n \"@typescript-es"
},
{
"path": ".gitignore",
"chars": 1031,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": ".husky/pre-commit",
"chars": 48,
"preview": "#!/usr/bin/env sh\nset -e\n\nnpm run verify:commit\n"
},
{
"path": ".husky/pre-push",
"chars": 46,
"preview": "#!/usr/bin/env sh\nset -e\n\nnpm run verify:push\n"
},
{
"path": ".nvmrc",
"chars": 8,
"preview": "22.14.0\n"
},
{
"path": ".tmp_check_task.ts",
"chars": 809,
"preview": "import { prisma } from '@/lib/prisma'\n\nconst id = 'a3cbc6d3-8720-4584-addd-e2bc4ace7759'\n\nasync function main() {\n cons"
},
{
"path": "CHANGELOG.md",
"chars": 549,
"preview": "# Changelog / 更新日志\n\nAll notable changes to this project will be documented in this file.\n\n---\n\n## [v0.2] - 2026-02-28\n\n#"
},
{
"path": "Dockerfile",
"chars": 1638,
"preview": "# ==================== Stage 1: Dependencies ====================\nFROM node:20-alpine AS deps\nWORKDIR /app\n\nCOPY package"
},
{
"path": "README.md",
"chars": 3927,
"preview": "<p align=\"center\">\n <a href=\"https://www.waoowaoo.com/\">\n <img src=\"images/cta-banner.png\" alt=\"🚀 探索 AI 影视的下一代创作流 | "
},
{
"path": "README_en.md",
"chars": 5107,
"preview": "<p align=\"center\">\n <img src=\"public/banner.png\" alt=\"waoowaoo\" width=\"600\">\n</p>\n\n<h1 align=\"center\">waoowaoo AI Video"
},
{
"path": "caddyfile",
"chars": 493,
"preview": "# HTTPS 反向代理(在主机上运行,非 Docker 内)\n# 启动方式: caddy run --config Caddyfile\n#\n# 用法:\n# 1. docker compose up -d (启动 App + My"
},
{
"path": "debug-request.json",
"chars": 276,
"preview": "{\n \"model\": \"doubao-seedream-4-0-250828\",\n \"prompt\": \"Lily and Olivia in 医院病房_日夜, medium shot, dramatic lighting, Amer"
},
{
"path": "docker-compose.test.yml",
"chars": 827,
"preview": "services:\n mysql:\n image: mysql:8.0\n container_name: waoowaoo-test-mysql\n environment:\n MYSQL_ROOT_PASSWO"
},
{
"path": "docker-compose.yml",
"chars": 4734,
"preview": "services:\n # ==================== MySQL ====================\n mysql:\n image: mysql:8.0\n container_name: waoowaoo"
},
{
"path": "eslint.config.mjs",
"chars": 1161,
"preview": "import { dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { FlatCompat } from \"@eslint/eslintrc\";\n\ncon"
},
{
"path": "extract_chinese.py",
"chars": 2977,
"preview": "#!/usr/bin/env python3\n\"\"\"\n提取React/TypeScript代码中的硬编码中文字符串\n\"\"\"\nimport re\nimport os\nfrom pathlib import Path\nimport json\n\n"
},
{
"path": "lib/prompts/character-reference/character_image_to_description.en.txt",
"chars": 951,
"preview": "# Character Image To Description Prompt\n\nAnalyze the provided character image and write one detailed English visual desc"
},
{
"path": "lib/prompts/character-reference/character_image_to_description.zh.txt",
"chars": 564,
"preview": "# 图片反推角色描述提示词\n\n请分析这张角色图片,生成一段详细的角色外貌描述(用于 AI 图片生成)。\n\n## 输出要求\n\n生成一段完整的角色视觉描述,包含以下要素:\n\n1. 性别和年龄段(如:约二十五岁的男性)\n2. 发型发色(如:黑色短"
},
{
"path": "lib/prompts/character-reference/character_reference_to_sheet.en.txt",
"chars": 1105,
"preview": "# Reference Image To Character Sheet Prompt (img2img)\n\nUse the provided reference image to extract face traits, hairstyl"
},
{
"path": "lib/prompts/character-reference/character_reference_to_sheet.zh.txt",
"chars": 458,
"preview": "# 参考图转角色设定图提示词(图生图模式)\n\n基于提供的参考图片,提取角色的面部五官特征、发型、体型和服装款式作为参考。\n\n## 画风优先级规则\n\n画风由用户选择的风格指令决定,严格遵循风格指令进行生成。\n参考图仅用于保持角色身份特征(五官"
},
{
"path": "lib/prompts/novel-promotion/agent_acting_direction.en.txt",
"chars": 1356,
"preview": "You are an experienced Acting Director.\nYour task is to generate acting notes for each character in each panel.\n\nCore in"
},
{
"path": "lib/prompts/novel-promotion/agent_acting_direction.zh.txt",
"chars": 1571,
"preview": "你是一位经验丰富的表演指导(Acting Director)。你的任务是为一组分镜中的**每个镜头**设计角色的表演细节。\n\n【核心职责】\n\n分析整组分镜后,为每个镜头中的角色用一句话描述完整的表演指令,包含:\n- 情绪状态与强度\n- 面部"
},
{
"path": "lib/prompts/novel-promotion/agent_character_profile.en.txt",
"chars": 3250,
"preview": "You are a casting and character-asset analyst.\nAnalyze the input text and produce structured character profiles for visu"
},
{
"path": "lib/prompts/novel-promotion/agent_character_profile.zh.txt",
"chars": 5147,
"preview": "你是专业的\"选角指导\"。请基于提供的文本(小说、剧本或混合格式),分析并输出所有需要制作形象的角色档案信息。\n\n【你的职责】\n- 识别需要在画面中出现的角色\n- 根据剧情发展和角色身份判断每个角色的重要性层级\n- 分析角色的性格和背景\n- "
},
{
"path": "lib/prompts/novel-promotion/agent_character_visual.en.txt",
"chars": 1527,
"preview": "You are a character visual designer.\nGenerate image-ready appearance descriptions from character profiles.\n\nCharacter pr"
},
{
"path": "lib/prompts/novel-promotion/agent_character_visual.zh.txt",
"chars": 3474,
"preview": "你是专业的\"角色视觉设计师\"。根据角色档案信息,生成详细的人物外貌描述(用于AI图片生成)。\n\n【你的职责】\n- 根据角色档案生成对应的外貌描述\n- 确保核心角色有明显的视觉辨识度\n- 体现角色性格和身份的视觉特征\n- 服装华丽度由角色身份"
},
{
"path": "lib/prompts/novel-promotion/agent_cinematographer.en.txt",
"chars": 947,
"preview": "You are a cinematography planner.\nFor each panel, generate a concise photography rule package.\n\nInputs:\n- Panel count: {"
},
{
"path": "lib/prompts/novel-promotion/agent_cinematographer.zh.txt",
"chars": 2486,
"preview": "你是一位经验丰富的电影摄影指导(Director of Photography)。你的任务是为一组分镜中的**每个镜头**分别设计摄影规则。\n\n【核心职责】\n\n分析整组分镜后,为每个镜头单独设计以下视觉要素:\n1. 灯光设置 - 光源方向和"
},
{
"path": "lib/prompts/novel-promotion/agent_clip.en.txt",
"chars": 1069,
"preview": "You are a story clip segmentation expert.\nSplit the full text into clip candidates for downstream screenplay conversion."
},
{
"path": "lib/prompts/novel-promotion/agent_clip.zh.txt",
"chars": 2176,
"preview": "你是\"剧本/文字片段预分割大师\"。\n任务:把用户输入给你的文字创意或剧本整份输入文字按场景/剧情边界切成若干批次,便于后续逐批转换为分镜。只输出 JSON,字段仅限如下结构,start为文字开始的文本,end为文字结束的文本,禁止任何多余文"
},
{
"path": "lib/prompts/novel-promotion/agent_shot_variant_analysis.en.txt",
"chars": 1011,
"preview": "You are a shot variant analysis expert.\nAnalyze the current shot and provide multiple strong variant ideas.\n\nCurrent sho"
},
{
"path": "lib/prompts/novel-promotion/agent_shot_variant_analysis.zh.txt",
"chars": 3395,
"preview": "你是专业的电影分镜分析师。你的任务是分析一个镜头画面,并推荐多种有创意的镜头变体方案。\n\n======================================\n【输入信息】\n============================="
},
{
"path": "lib/prompts/novel-promotion/agent_shot_variant_generate.en.txt",
"chars": 989,
"preview": "You are a storyboard image generation assistant.\nGenerate one new variant image that keeps identity/style continuity whi"
},
{
"path": "lib/prompts/novel-promotion/agent_shot_variant_generate.zh.txt",
"chars": 1367,
"preview": "你是专业的分镜图像生成助手。\n\n======================================\n【任务】\n======================================\n\n基于参考图片和变体指令,生成一个新的镜头"
},
{
"path": "lib/prompts/novel-promotion/agent_storyboard_detail.en.txt",
"chars": 1401,
"preview": "You are a senior storyboard detail refiner.\nRefine panel-level visual details and video prompts.\n\nPanel input JSON:\n{pan"
},
{
"path": "lib/prompts/novel-promotion/agent_storyboard_detail.zh.txt",
"chars": 3394,
"preview": "你是顶级电影分镜师。根据分镜规划和场景类型,设计镜头语言和视频提示词。\n\n【你的职责】\n- 根据scene_type选择镜头风格\n- 为每个分镜设计景别、视角、镜头运动\n- 撰写video_prompt(用年龄段+性别替代角色名)\n- ⚠️"
},
{
"path": "lib/prompts/novel-promotion/agent_storyboard_insert.en.txt",
"chars": 1235,
"preview": "You are a storyboard insertion assistant.\nInsert one transition panel between two existing panels.\n\nPrevious panel (inse"
},
{
"path": "lib/prompts/novel-promotion/agent_storyboard_insert.zh.txt",
"chars": 2553,
"preview": "你是专业的分镜插入助手。你的任务是在两个已有镜头之间,生成一个自然过渡的单个分镜。\n\n【任务背景】\n用户需要在已有的分镜序列中插入一个新镜头。你需要分析前后两个镜头的内容、角色、场景、镜头语言,生成一个连贯过渡的分镜。\n\n========="
},
{
"path": "lib/prompts/novel-promotion/agent_storyboard_plan.en.txt",
"chars": 1480,
"preview": "You are a storyboard planning director.\nGenerate an initial panel sequence for one clip.\n\nCharacter library names:\n{char"
},
{
"path": "lib/prompts/novel-promotion/agent_storyboard_plan.zh.txt",
"chars": 6403,
"preview": "你是专业的分镜规划师。你的任务是根据剧本内容(或原文)将故事拆解成连续的分镜头,设计分镜板基础规划。\n\n输入可能是两种格式:\n1. 【剧本格式】JSON格式的结构化剧本,包含scenes、action、dialogue、voiceover等"
},
{
"path": "lib/prompts/novel-promotion/character_create.en.txt",
"chars": 774,
"preview": "You are a professional character prompt designer.\nGenerate one image-ready character appearance prompt from the user's r"
},
{
"path": "lib/prompts/novel-promotion/character_create.zh.txt",
"chars": 1585,
"preview": "请按照以下提示词规则执行用户的生成人物需求\n【人物生成要求(用于出图,中文描述)】\n\n1. 生成1条详细的中文外貌描述,供AI图片生成使用\n\n2. 描述要求突出角色特色,有细节质感:\n - 性别、年龄范围(写具体年龄区间,如\"约二十五岁"
},
{
"path": "lib/prompts/novel-promotion/character_description_update.en.txt",
"chars": 710,
"preview": "You are a character appearance prompt editor.\nUpdate the original character description according to the user's edit ins"
},
{
"path": "lib/prompts/novel-promotion/character_description_update.zh.txt",
"chars": 479,
"preview": "你是一个专业的角色形象描述更新专家。\n\n【任务】\n根据用户对角色图片的修改,更新角色的形象描述词。\n\n【原始角色描述】\n{original_description}\n\n【用户修改指令】\n{modify_instruction}\n\n{imag"
},
{
"path": "lib/prompts/novel-promotion/character_modify.en.txt",
"chars": 679,
"preview": "You are a professional character prompt modifier.\nModify an existing character description based on user instruction.\n\nC"
},
{
"path": "lib/prompts/novel-promotion/character_modify.zh.txt",
"chars": 1518,
"preview": "请按照以下规则执行用户的人物生成提示词修改需求\n1:人物规则按照以下规则修改\n【人物生成要求(用于出图,中文描述)】\n\n1. 生成1条详细的中文外貌描述,供AI图片生成使用\n\n2. 描述要求突出角色特色,有细节质感:\n - 性别、年龄范"
},
{
"path": "lib/prompts/novel-promotion/character_regenerate.en.txt",
"chars": 877,
"preview": "You are a character appearance regenerator.\nGenerate 3 new character appearance variants based on story context.\n\nCharac"
},
{
"path": "lib/prompts/novel-promotion/character_regenerate.zh.txt",
"chars": 1274,
"preview": "你是\"角色形象重塑师\"。请根据小说原文,为指定角色重新生成 3 条全新的外貌描述。\n\n【角色信息】\n- 角色名:{character_name}\n- 形象类型:{change_reason}\n- 当前描述(参考,需要生成不同的):\n{cur"
},
{
"path": "lib/prompts/novel-promotion/episode_split.en.txt",
"chars": 1111,
"preview": "You are a long-text episode splitter.\nAnalyze the full text and split it into balanced episodes.\n\nInput text:\n{CONTENT}\n"
},
{
"path": "lib/prompts/novel-promotion/episode_split.zh.txt",
"chars": 1813,
"preview": "你是一个专业的内容分析助手。请分析以下文本,将其智能分割为多个剧集。\n\n## ⚠️ 核心规则:字数必须均衡(最重要)\n\n**所有剧集的字数必须尽可能均衡!偏差不得超过 ±20%**\n\n### 📊 第一步:精确计算(必须执行)\n\n1. 统计总"
},
{
"path": "lib/prompts/novel-promotion/image_prompt_modify.en.txt",
"chars": 696,
"preview": "You are a prompt refinement expert for storyboard image/video generation.\n\nCurrent image prompt:\n{prompt_input}\n\nCurrent"
},
{
"path": "lib/prompts/novel-promotion/image_prompt_modify.zh.txt",
"chars": 1421,
"preview": "请按照以下提示词规则执行用户的ai生图以及视频运动提示词场景需求\n1. 详细描述镜头角度和地点、动作、人物、环境,也就是谁、和谁、在哪里、干了什么,如果是空镜标题等,那么也要描述出原文想要表达的东西,例如作者名字,或者和原文有关的内容,提示"
},
{
"path": "lib/prompts/novel-promotion/location_create.en.txt",
"chars": 666,
"preview": "You are a professional environment prompt designer.\nGenerate one scene prompt for image generation.\n\nUser request:\n{user"
},
{
"path": "lib/prompts/novel-promotion/location_create.zh.txt",
"chars": 682,
"preview": "请按照以下提示词规则执行用户的生成场景需求\n\n【场景生成要求(用于出图,中文描述)】\n\n1. 生成1条中文环境描述(60-120字),像真实摄影场景一样描述\n\n2. **开头必须明确写明场景名称**:\n - 描述开头必须以\"【场景名称】"
},
{
"path": "lib/prompts/novel-promotion/location_description_update.en.txt",
"chars": 688,
"preview": "You are a scene description editor.\nUpdate the original location description based on user instruction.\n\nLocation name:\n"
},
{
"path": "lib/prompts/novel-promotion/location_description_update.zh.txt",
"chars": 592,
"preview": "你是一个专业的场景描述更新专家。\n\n【任务】\n根据用户对场景图片的修改,更新场景的描述词。\n\n【场景名称】\n{location_name}\n\n【原始场景描述】\n{original_description}\n\n【用户修改指令】\n{modify"
},
{
"path": "lib/prompts/novel-promotion/location_modify.en.txt",
"chars": 638,
"preview": "You are a professional scene prompt modifier.\nModify an existing scene description while preserving scene identity.\n\nLoc"
},
{
"path": "lib/prompts/novel-promotion/location_modify.zh.txt",
"chars": 1493,
"preview": "请按照以下提示词规则执行用户的修改场景需求\n\n【场景名称】\n{location_name}\n\n【场景生成要求(用于出图,中文描述)】\n\n1. **开头必须明确写明场景名称**:\n - 描述开头必须以「{location_name}」的形"
},
{
"path": "lib/prompts/novel-promotion/location_regenerate.en.txt",
"chars": 748,
"preview": "You are a scene variant regenerator.\nGenerate 3 new scene description variants for the same location.\n\nLocation name:\n{l"
},
{
"path": "lib/prompts/novel-promotion/location_regenerate.zh.txt",
"chars": 945,
"preview": "你是\"场景重塑师\"。请根据当前的场景描述,为指定场景重新生成 3 条全新的场景描述变体。\n\n【场景信息】\n- 场景名:{location_name}\n- 当前描述(作为参考,需要生成不同的变体):\n{current_descriptions"
},
{
"path": "lib/prompts/novel-promotion/screenplay_conversion.en.txt",
"chars": 1650,
"preview": "You are a screenplay conversion specialist.\nConvert the clip text into structured screenplay JSON without adding new sto"
},
{
"path": "lib/prompts/novel-promotion/screenplay_conversion.zh.txt",
"chars": 4919,
"preview": "你是专业的编剧和剧本改编师。你的任务是将小说/文学文本转换为标准的影视剧本格式。\n\n⚠️⚠️⚠️【最高优先级原则 - 100%忠实原文】⚠️⚠️⚠️\n\n你的工作是**格式转换**,不是**创作**!你必须100%忠实于原文,严禁任何形式的\""
},
{
"path": "lib/prompts/novel-promotion/select_location.en.txt",
"chars": 1392,
"preview": "You are a location asset extraction specialist.\nExtract locations that need dedicated background assets.\n\nInput text:\n{i"
},
{
"path": "lib/prompts/novel-promotion/select_location.zh.txt",
"chars": 3097,
"preview": "你是\"场景资产建立师\"。请基于我提供的文本(可能是小说、剧本、或混合格式),筛选【需要制作画面的场景】,生成用于出图与后续生产的资产 JSON。\n\n【筛选规则 - 精准提取模式】\n\n✅【必须提取的场景】:\n - 剧本场景头部中出现的地点("
},
{
"path": "lib/prompts/novel-promotion/single_panel_image.en.txt",
"chars": 797,
"preview": "You are a professional storyboard image artist.\nGenerate exactly one high-quality image for one panel.\n\nAbsolute constra"
},
{
"path": "lib/prompts/novel-promotion/single_panel_image.zh.txt",
"chars": 1333,
"preview": "你是一位专业的分镜画师。请根据以下分镜数据生成单张高质量的镜头图片。\n\n【绝对禁止 - 图像中不得出现任何文字 - 最高优先级】\n生成的图像中绝对禁止出现任何文字:\n- 禁止出现镜头类型标签(特写、中景、全景等)\n- 禁止出现镜头运动文字("
},
{
"path": "lib/prompts/novel-promotion/storyboard_edit.en.txt",
"chars": 400,
"preview": "You are an expert storyboard image editor.\nEdit a single panel image or a panel set according to user instruction.\n\nRule"
},
{
"path": "lib/prompts/novel-promotion/storyboard_edit.zh.txt",
"chars": 154,
"preview": "你是一个修改编辑图片的大师,你需要根据用户的指令来编辑单个镜头或整组分镜图片,编辑时需要遵守以下规则:\n1:不要添加任何多余标识符文字,如字幕,景别信息等\n2:如果用户上传的有图片,那么就按照图片来进行参考修改\n\n用户的编辑指令如下:{us"
},
{
"path": "lib/prompts/novel-promotion/voice_analysis.en.txt",
"chars": 1265,
"preview": "You are a dialogue voice-line analyzer.\nExtract spoken lines from text, assign speaker, estimate emotion intensity, and "
},
{
"path": "lib/prompts/novel-promotion/voice_analysis.zh.txt",
"chars": 2763,
"preview": "你是\"台词发言人分析大师\"。\n任务:从文本中提取需要配音的**对话台词**,分析情绪强度,并匹配对应的视频镜头。\n\n输出格式(只返回JSON,禁止markdown标记):\n[\n {\n \"lineIndex\": 1,\n \"spe"
},
{
"path": "lib/prompts/proxy.ts",
"chars": 305,
"preview": "\nexport async function setProxy() {\n if (process.env.PROXY_URL) { // If you are in China, you must use this proxy:\n "
},
{
"path": "lib/prompts/skills/api-config-template.system.txt",
"chars": 2378,
"preview": "你是 API 配置助手,负责把第三方 API 文档映射为可执行模型模板并保存。\n\n目标:\n1) 最少追问,只收集运行必填字段。\n2) 字段齐全后立刻调用工具保存。\n3) 绝不输出臆测字段;不确定就追问。\n\n工具调用规则:\n1) 单模型保存:"
},
{
"path": "lib/prompts/skills/tutorial.system.txt",
"chars": 70,
"preview": "你是产品教程助手。\n目标是给出准确、可执行、简洁的操作步骤。\n当用户信息不足时先提问,不要猜测。\n禁止编造不存在的页面、按钮、接口或参数。\n"
},
{
"path": "messages/en/actions.json",
"chars": 553,
"preview": "{\n \"storyboard\": \"Storyboard\",\n \"storyboard_candidate\": \"Storyboard Candidate\",\n \"character\": \"Character\",\n \"locatio"
},
{
"path": "messages/en/apiConfig.json",
"chars": 9608,
"preview": "{\n \"title\": \"API Configuration\",\n \"saving\": \"Saving...\",\n \"saved\": \"Saved\",\n \"saveFailed\": \"Save failed\",\n \"connect"
},
{
"path": "messages/en/apiTypes.json",
"chars": 206,
"preview": "{\n \"image\": \"Image Generation\",\n \"video\": \"Video Generation\",\n \"text\": \"Text Analysis\",\n \"tts\": \"Text-to-Speech\",\n "
},
{
"path": "messages/en/assetHub.json",
"chars": 4654,
"preview": "{\n \"title\": \"Asset Hub\",\n \"description\": \"Manage your global character and location assets\",\n \"modelHint\": \"Ass"
},
{
"path": "messages/en/assetLibrary.json",
"chars": 386,
"preview": "{\n \"title\": \"Asset Library\",\n \"button\": \"Assets\",\n \"characters\": \"Characters\",\n \"locations\": \"Locations\",\n \"noChara"
},
{
"path": "messages/en/assetModal.json",
"chars": 4244,
"preview": "{\n \"character\": {\n \"title\": \"New Character\",\n \"name\": \"Character Name\",\n \"namePlaceholder\": \"Ent"
},
{
"path": "messages/en/assetPicker.json",
"chars": 682,
"preview": "{\n \"selectCharacter\": \"Select Character from Asset Hub\",\n \"selectLocation\": \"Select Location from Asset Hub\",\n "
},
{
"path": "messages/en/assets.json",
"chars": 15539,
"preview": "{\n \"stage\": {\n \"title\": \"Assets Confirmation\",\n \"characters\": \"Characters\",\n \"locations\": \"Locat"
},
{
"path": "messages/en/auth.json",
"chars": 1519,
"preview": "{\n \"welcomeBack\": \"Welcome Back\",\n \"loginTo\": \"Sign in to waoowaoo\",\n \"createAccount\": \"Create Account\",\n \"joinPlatf"
},
{
"path": "messages/en/billing.json",
"chars": 369,
"preview": "{\n \"transactionType\": \"Transaction Type\",\n \"startDate\": \"Start Date\",\n \"endDate\": \"End Date\",\n \"all\": \"All\",\n \"inco"
},
{
"path": "messages/en/common.json",
"chars": 4703,
"preview": "{\n \"appName\": \"waoowaoo\",\n \"betaVersion\": \"Beta v{version}\",\n \"updateNotice\": {\n \"title\": \"New version a"
},
{
"path": "messages/en/configModal.json",
"chars": 1228,
"preview": "{\n \"title\": \"Project Config\",\n \"subtitle\": \"Defaults to the global settings. You can customize models for this pro"
},
{
"path": "messages/en/errors.json",
"chars": 1557,
"preview": "{\n \"UNAUTHORIZED\": \"Please log in first\",\n \"FORBIDDEN\": \"Access denied\",\n \"NOT_FOUND\": \"Resource not found\",\n "
},
{
"path": "messages/en/landing.json",
"chars": 819,
"preview": "{\n \"title\": \"waoowaoo\",\n \"subtitle\": \"AI Film & TV Studio\",\n \"enterWorkspace\": \"Enter Workspace\",\n \"loading\": \"Loadi"
},
{
"path": "messages/en/layout.json",
"chars": 136,
"preview": "{\n \"title\": \"AI Anime Production Platform\",\n \"description\": \"Create professional anime content with cutting-edge A"
},
{
"path": "messages/en/modelSection.json",
"chars": 835,
"preview": "{\n \"llmModels\": \"Text Model List\",\n \"imageModels\": \"Image Model List\",\n \"videoModels\": \"Video Model List\",\n \"price\":"
},
{
"path": "messages/en/nav.json",
"chars": 186,
"preview": "{\n \"workspace\": \"Workspace\",\n \"assetHub\": \"Asset Hub\",\n \"profile\": \"Settings\",\n \"downloadLogs\": \"Download Logs\",\n \""
},
{
"path": "messages/en/novel-promotion.json",
"chars": 7717,
"preview": "{\n \"stages\": {\n \"story\": \"Story\",\n \"script\": \"Script\",\n \"storyboard\": \"Storyboard\",\n \"video\": \"Video\",\n "
},
{
"path": "messages/en/profile.json",
"chars": 4139,
"preview": "{\n \"user\": \"User\",\n \"personalAccount\": \"Personal Account\",\n \"availableBalance\": \"Available Balance\",\n \"openSourceNoB"
},
{
"path": "messages/en/progress.json",
"chars": 5782,
"preview": "{\n \"analyzing\": \"Analyzing story structure...\",\n \"splittingClips\": \"Splitting into clips...\",\n \"convertingScreenplay\""
},
{
"path": "messages/en/providerSection.json",
"chars": 141,
"preview": "{\n \"addProvider\": \"+ Add Provider\",\n \"name\": \"Name\",\n \"add\": \"Add\",\n \"save\": \"Save\",\n \"fillRequired\": \"Please fill "
},
{
"path": "messages/en/scriptView.json",
"chars": 2697,
"preview": "{\n \"title\": \"Script View\",\n \"scriptBreakdown\": \"Script Breakdown\",\n \"splitCount\": \"{count} clips split\",\n \"n"
},
{
"path": "messages/en/smartImport.json",
"chars": 6832,
"preview": "{\n \"title\": \"Start Your Creative Journey\",\n \"subtitle\": \"First, choose your creation method\",\n \"manualCreate\": {\n "
},
{
"path": "messages/en/stages.json",
"chars": 167,
"preview": "{\n \"config\": \"1. Config\",\n \"assets\": \"2. Asset Analysis\",\n \"storyboard\": \"3. Storyboard Edit\",\n \"videos\": \"4. Video "
},
{
"path": "messages/en/storyboard.json",
"chars": 14842,
"preview": "{\n \"phases\": {\n \"planning\": \"Planning Storyboard\",\n \"cinematography\": \"Cinematography Design\",\n "
},
{
"path": "messages/en/video.json",
"chars": 6758,
"preview": "{\n \"panelCard\": {\n \"play\": \"Play\",\n \"pause\": \"Pause\",\n \"retry\": \"Retry\",\n \"regenerate\": \"Regenerate\",\n \""
},
{
"path": "messages/en/voice.json",
"chars": 11127,
"preview": "{\n \"title\": \"Voice Lines\",\n \"linesCount\": \"{count} lines total, \",\n \"audioGeneratedCount\": \"{count} audio gener"
},
{
"path": "messages/en/workspace.json",
"chars": 1617,
"preview": "{\n \"title\": \"My Projects\",\n \"subtitle\": \"Manage your AI anime production projects\",\n \"newProject\": \"New Project\",\n \""
},
{
"path": "messages/en/workspaceDetail.json",
"chars": 1544,
"preview": "{\n \"globalAssets\": \"Global Assets\",\n \"createFailed\": \"Creation failed\",\n \"deleteFailed\": \"Deletion failed\",\n \"rename"
},
{
"path": "messages/en/worldContextModal.json",
"chars": 541,
"preview": "{\n \"title\": \"World & Character Settings\",\n \"description\": \"Define global character appearances, scene styles, and "
},
{
"path": "messages/zh/actions.json",
"chars": 404,
"preview": "{\n \"storyboard\": \"分镜图\",\n \"storyboard_candidate\": \"分镜候选\",\n \"character\": \"角色图\",\n \"location\": \"场景图\",\n \"video\": \"视频\",\n "
},
{
"path": "messages/zh/apiConfig.json",
"chars": 6431,
"preview": "{\n \"title\": \"API 配置\",\n \"saving\": \"保存中...\",\n \"saved\": \"已保存\",\n \"saveFailed\": \"保存失败\",\n \"connected\": \"已连接\",\n \"notConfi"
},
{
"path": "messages/zh/apiTypes.json",
"chars": 140,
"preview": "{\n \"image\": \"图片生成\",\n \"video\": \"视频生成\",\n \"text\": \"文本分析\",\n \"tts\": \"语音合成\",\n \"voice\": \"配音\",\n \"voice_design\": \"声音设计\",\n "
},
{
"path": "messages/zh/assetHub.json",
"chars": 3342,
"preview": "{\n \"title\": \"资产中心\",\n \"description\": \"管理您的全局角色和场景资产\",\n \"modelHint\": \"资产中心使用默认模型,如需修改请在\",\n \"modelHintLink\": \"A"
},
{
"path": "messages/zh/assetLibrary.json",
"chars": 292,
"preview": "{\n \"title\": \"资产库\",\n \"button\": \"资产库\",\n \"characters\": \"角色\",\n \"locations\": \"场景\",\n \"noCharacters\": \"暂无角色\",\n \"noLocatio"
},
{
"path": "messages/zh/assetModal.json",
"chars": 2997,
"preview": "{\n \"character\": {\n \"title\": \"新建角色\",\n \"name\": \"角色名称\",\n \"namePlaceholder\": \"请输入角色名称\",\n \"mod"
},
{
"path": "messages/zh/assetPicker.json",
"chars": 474,
"preview": "{\n \"selectCharacter\": \"从资产中心选择角色\",\n \"selectLocation\": \"从资产中心选择场景\",\n \"selectVoice\": \"从资产中心选择音色\",\n \"searchPlac"
},
{
"path": "messages/zh/assets.json",
"chars": 11371,
"preview": "{\n \"stage\": {\n \"title\": \"资产确认\",\n \"characters\": \"角色\",\n \"locations\": \"场景\",\n \"analyze\": \"分析资"
},
{
"path": "messages/zh/auth.json",
"chars": 1001,
"preview": "{\n \"welcomeBack\": \"欢迎回来\",\n \"loginTo\": \"登录到waoowaoo\",\n \"createAccount\": \"创建账户\",\n \"joinPlatform\": \"加入waoowaoo\",\n \"pho"
},
{
"path": "messages/zh/billing.json",
"chars": 287,
"preview": "{\n \"transactionType\": \"交易类型\",\n \"startDate\": \"开始日期\",\n \"endDate\": \"结束日期\",\n \"all\": \"全部\",\n \"income\": \"收入\",\n \"expense\":"
},
{
"path": "messages/zh/common.json",
"chars": 3895,
"preview": "{\n \"appName\": \"waoowaoo\",\n \"betaVersion\": \"Beta v{version}\",\n \"updateNotice\": {\n \"title\": \"发现新版本\",\n "
},
{
"path": "messages/zh/configModal.json",
"chars": 894,
"preview": "{\n \"title\": \"项目配置\",\n \"subtitle\": \"默认沿用设置中心的全局配置,也可为当前项目单独自定义,修改仅对本项目生效。\",\n \"saved\": \"已保存\",\n \"autoSave\": \"自动保"
},
{
"path": "messages/zh/errors.json",
"chars": 916,
"preview": "{\n \"UNAUTHORIZED\": \"请先登录\",\n \"FORBIDDEN\": \"没有权限访问\",\n \"NOT_FOUND\": \"资源不存在\",\n \"INSUFFICIENT_BALANCE\": \"API余额不足,"
},
{
"path": "messages/zh/landing.json",
"chars": 577,
"preview": "{\n \"title\": \"waoowaoo\",\n \"subtitle\": \"AI影视Studio\",\n \"enterWorkspace\": \"进入工作区\",\n \"loading\": \"加载中...\",\n \"getStarted\":"
},
{
"path": "messages/zh/layout.json",
"chars": 71,
"preview": "{\n \"title\": \"AI动漫制作工作台\",\n \"description\": \"使用最先进的AI技术创建专业级动漫内容\"\n}\n"
},
{
"path": "messages/zh/modelSection.json",
"chars": 616,
"preview": "{\n \"llmModels\": \"文本模型列表\",\n \"imageModels\": \"图片模型列表\",\n \"videoModels\": \"视频模型列表\",\n \"price\": \"价格\",\n \"pricePerMillion\": \""
},
{
"path": "messages/zh/nav.json",
"chars": 150,
"preview": "{\n \"workspace\": \"工作区\",\n \"assetHub\": \"资产中心\",\n \"profile\": \"设置中心\",\n \"downloadLogs\": \"下载日志\",\n \"signin\": \"登录\",\n \"signup"
},
{
"path": "messages/zh/novel-promotion.json",
"chars": 4906,
"preview": "{\n \"stages\": {\n \"story\": \"故事\",\n \"script\": \"剧本\",\n \"storyboard\": \"分镜\",\n \"video\": \"成片\",\n \"editor\": \"AI剪辑\",\n"
},
{
"path": "messages/zh/profile.json",
"chars": 3472,
"preview": "{\n \"user\": \"用户\",\n \"personalAccount\": \"个人账户\",\n \"availableBalance\": \"可用余额\",\n \"openSourceNoBilling\": \"开源版本,无需计费"
},
{
"path": "messages/zh/progress.json",
"chars": 4177,
"preview": "{\n \"analyzing\": \"正在分析故事结构...\",\n \"splittingClips\": \"正在分割片段...\",\n \"convertingScreenplay\": \"正在转换为分镜脚本...\",\n \"submitting"
},
{
"path": "messages/zh/providerSection.json",
"chars": 106,
"preview": "{\n \"addProvider\": \"+ 添加提供商\",\n \"name\": \"名称\",\n \"add\": \"添加\",\n \"save\": \"保存\",\n \"fillRequired\": \"请填写必要信息\"\n}"
},
{
"path": "messages/zh/scriptView.json",
"chars": 2013,
"preview": "{\n \"title\": \"剧本视图\",\n \"scriptBreakdown\": \"剧本拆解\",\n \"splitCount\": \"已拆分 {count} 个分镜\",\n \"noClips\": \"暂无分镜,请先在故事视图生"
},
{
"path": "messages/zh/smartImport.json",
"chars": 4579,
"preview": "{\n \"title\": \"开启你的创作之旅\",\n \"subtitle\": \"首先,选择你的创作方式\",\n \"manualCreate\": {\n \"title\": \"从第一集开始创作\",\n \"description\": \"从"
},
{
"path": "messages/zh/stages.json",
"chars": 118,
"preview": "{\n \"config\": \"1. 配置\",\n \"assets\": \"2. 资产分析\",\n \"storyboard\": \"3. 分镜编辑\",\n \"videos\": \"4. 视频生成\",\n \"voice\": \"5. 配音生成\"\n}"
},
{
"path": "messages/zh/storyboard.json",
"chars": 11312,
"preview": "{\n \"phases\": {\n \"planning\": \"规划分镜\",\n \"cinematography\": \"设计摄影\",\n \"acting\": \"设计演技\",\n \"detai"
},
{
"path": "messages/zh/video.json",
"chars": 5196,
"preview": "{\n \"panelCard\": {\n \"play\": \"播放\",\n \"pause\": \"暂停\",\n \"retry\": \"重试\",\n \"regenerate\": \"重新生成\",\n \"download\": \"下载"
},
{
"path": "messages/zh/voice.json",
"chars": 8293,
"preview": "{\n \"title\": \"台词配音\",\n \"linesCount\": \"共 {count} 条台词,\",\n \"audioGeneratedCount\": \"{count} 条已生成音频\",\n \"emotionProm"
},
{
"path": "messages/zh/workspace.json",
"chars": 1045,
"preview": "{\n \"title\": \"我的项目\",\n \"subtitle\": \"管理您的AI动漫制作项目\",\n \"newProject\": \"新建项目\",\n \"searchPlaceholder\": \"搜索项目名称或描述...\",\n \"sea"
},
{
"path": "messages/zh/workspaceDetail.json",
"chars": 1019,
"preview": "{\n \"globalAssets\": \"全局资产\",\n \"createFailed\": \"创建失败\",\n \"deleteFailed\": \"删除失败\",\n \"renameFailed\": \"重命名失败\",\n \"refreshFai"
},
{
"path": "messages/zh/worldContextModal.json",
"chars": 243,
"preview": "{\n \"title\": \"世界观与人设\",\n \"description\": \"定义全局通用的角色外貌、场景风格和环境描述\",\n \"placeholder\": \"例如:\\n【男主】张三,25岁,黑色短发,总是穿着一件洗得发白"
},
{
"path": "middleware.ts",
"chars": 386,
"preview": "import createMiddleware from 'next-intl/middleware';\nimport { routing } from './src/i18n/routing';\n\nexport default creat"
},
{
"path": "next.config.ts",
"chars": 440,
"preview": "import type { NextConfig } from \"next\";\nimport createNextIntlPlugin from 'next-intl/plugin';\n\nconst withNextIntl = creat"
},
{
"path": "package.json",
"chars": 11929,
"preview": "{\n \"name\": \"waoowaoo\",\n \"version\": \"0.3.0\",\n \"private\": true,\n \"engines\": {\n \"node\": \">=18.18.0\",\n \"npm\": \">=9"
},
{
"path": "postcss.config.mjs",
"chars": 81,
"preview": "const config = {\n plugins: [\"@tailwindcss/postcss\"],\n};\n\nexport default config;\n"
},
{
"path": "prisma/schema.prisma",
"chars": 38395,
"preview": "generator client {\n provider = \"prisma-client-js\"\n}\n\ndatasource db {\n provider = \"mysql\"\n url = env(\"DATABASE_UR"
},
{
"path": "prisma/schema.sqlit.prisma",
"chars": 30520,
"preview": "generator client {\n provider = \"prisma-client-js\"\n}\n\ndatasource db {\n provider = \"sqlite\"\n url = env(\"DATABASE_U"
},
{
"path": "scripts/billing-cleanup-pending-freezes.ts",
"chars": 3570,
"preview": "import { prisma } from '@/lib/prisma'\nimport { toMoneyNumber } from '@/lib/billing/money'\n\ntype CleanupStats = {\n scann"
},
{
"path": "scripts/billing-reconcile-ledger.ts",
"chars": 3379,
"preview": "import { prisma } from '@/lib/prisma'\nimport { roundMoney, toMoneyNumber } from '@/lib/billing/money'\n\ntype UserLedgerRo"
},
{
"path": "scripts/bull-board.ts",
"chars": 2855,
"preview": "import { createScopedLogger } from '@/lib/logging/core'\nimport express, { type NextFunction, type Request, type Response"
},
{
"path": "scripts/check-api-handler.ts",
"chars": 1069,
"preview": "import { logInfo as _ulogInfo, logError as _ulogError } from '@/lib/logging/core'\nimport { execSync } from 'node:child_p"
},
{
"path": "scripts/check-capability-catalog.mjs",
"chars": 12975,
"preview": "import { promises as fs } from 'node:fs'\nimport path from 'node:path'\n\n const CATALOG_DIR = path.resolve(process.cwd(), "
},
{
"path": "scripts/check-image-urls-contract.ts",
"chars": 3209,
"preview": "import { logInfo as _ulogInfo, logError as _ulogError } from '@/lib/logging/core'\nimport { prisma } from '@/lib/prisma'\n"
},
{
"path": "scripts/check-log-semantic.ts",
"chars": 2850,
"preview": "import fs from 'node:fs'\n\ntype Rule = {\n file: string\n patterns: string[]\n}\n\nconst RULES: Rule[] = [\n {\n file: 'sr"
},
{
"path": "scripts/check-media-normalization.ts",
"chars": 3517,
"preview": "import { execSync } from 'node:child_process'\n\nconst TARGETS = ['src/app/api', 'src/lib']\n\nconst EXTRACT_ALLOWLIST = new"
},
{
"path": "scripts/check-model-config-contract.mjs",
"chars": 14730,
"preview": "let prisma\n\nconst STRICT = process.argv.includes('--strict')\nconst MODEL_FIELDS = [\n 'analysisModel',\n 'characterModel"
},
{
"path": "scripts/check-no-console.ts",
"chars": 1517,
"preview": "import { execSync } from 'node:child_process'\n\nconst ALLOWLIST = new Set<string>([\n 'src/lib/logging/core.ts',\n 'src/l"
},
{
"path": "scripts/check-outbound-image-runtime-sample.ts",
"chars": 9584,
"preview": "import { prisma } from '@/lib/prisma'\nimport { TASK_TYPE } from '@/lib/task/types'\n\ntype AnyJson = unknown\n\ntype Match ="
},
{
"path": "scripts/check-outbound-image-success-rate.ts",
"chars": 6455,
"preview": "import { prisma } from '@/lib/prisma'\nimport { TASK_STATUS, TASK_TYPE } from '@/lib/task/types'\n\ntype StatusCount = Reco"
},
{
"path": "scripts/check-outbound-image-unification.ts",
"chars": 6226,
"preview": "import fs from 'node:fs'\nimport path from 'node:path'\n\ntype Rule = {\n file: string\n pattern: RegExp\n message: string\n"
},
{
"path": "scripts/check-pricing-catalog.mjs",
"chars": 10346,
"preview": "import { promises as fs } from 'node:fs'\nimport path from 'node:path'\n\nconst CATALOG_DIR = path.resolve(process.cwd(), '"
},
{
"path": "scripts/cleanup-remove-legacy-voice-data.ts",
"chars": 4687,
"preview": "import { prisma } from '@/lib/prisma'\n\ntype CharacterVoiceRecord = {\n id: string\n customVoiceUrl: string | null\n}\n\ntyp"
},
{
"path": "scripts/diagnose-project.ts",
"chars": 5430,
"preview": "/**\n * 诊断项目任务状态\n * 运行: npx tsx scripts/diagnose-project.ts <projectId>\n */\nimport { config } from 'dotenv'\nconfig()\n\nimp"
},
{
"path": "scripts/guards/api-route-contract-guard.mjs",
"chars": 3302,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\nimport { pathToFileURL } "
},
{
"path": "scripts/guards/changed-file-test-impact-guard.mjs",
"chars": 3511,
"preview": "#!/usr/bin/env node\n\nimport { execSync } from 'node:child_process'\nimport { pathToFileURL } from 'node:url'\n\nconst RULES"
},
{
"path": "scripts/guards/file-line-count-guard.mjs",
"chars": 2233,
"preview": "#!/usr/bin/env node\nimport fs from 'fs'\nimport path from 'path'\n\nconst ROOT = process.cwd()\n\nconst RULES = [\n {\n lab"
},
{
"path": "scripts/guards/image-reference-normalization-guard.mjs",
"chars": 3231,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\nimport { pathToFileURL } "
},
{
"path": "scripts/guards/locale-navigation-guard.mjs",
"chars": 3318,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst root = process.cwd"
},
{
"path": "scripts/guards/no-api-direct-llm-call.mjs",
"chars": 2296,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst root = process.cwd"
},
{
"path": "scripts/guards/no-duplicate-endpoint-entry.mjs",
"chars": 1169,
"preview": "#!/usr/bin/env node\nimport fs from 'fs'\nimport path from 'path'\n\nconst ROOT = process.cwd()\nconst API_ROOT = path.join(R"
},
{
"path": "scripts/guards/no-hardcoded-model-capabilities.mjs",
"chars": 2049,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst root = process.cwd"
},
{
"path": "scripts/guards/no-internal-task-sync-fallback.mjs",
"chars": 2426,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst root = process.cwd"
},
{
"path": "scripts/guards/no-media-provider-bypass.mjs",
"chars": 2879,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst root = process.cwd"
},
{
"path": "scripts/guards/no-model-key-downgrade.mjs",
"chars": 3079,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst root = process.cwd"
},
{
"path": "scripts/guards/no-multiple-sources-of-truth.mjs",
"chars": 3299,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst root = process.cwd"
},
{
"path": "scripts/guards/no-provider-guessing.mjs",
"chars": 3047,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst root = process.cwd"
},
{
"path": "scripts/guards/no-server-mirror-state.mjs",
"chars": 2220,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst root = process.cwd"
},
{
"path": "scripts/guards/prompt-ab-regression.mjs",
"chars": 5008,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst root = process.cwd"
},
{
"path": "scripts/guards/prompt-i18n-guard.mjs",
"chars": 5200,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst root = process.cwd"
},
{
"path": "scripts/guards/prompt-json-canary-guard.mjs",
"chars": 10336,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst root = process.cwd"
},
{
"path": "scripts/guards/prompt-semantic-regression.mjs",
"chars": 4288,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst root = process.cwd"
},
{
"path": "scripts/guards/task-loading-baseline.json",
"chars": 283,
"preview": "{\n \"allowedDirectTaskStateUsageFiles\": [\n \"src/lib/query/hooks/useTaskTargetStates.ts\",\n \"src/lib/query/hooks/use"
},
{
"path": "scripts/guards/task-loading-guard.mjs",
"chars": 4551,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst workspaceRoot = pr"
},
{
"path": "scripts/guards/task-state-unification-guard.sh",
"chars": 849,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nfailed=0\n\ncheck_absent() {\n local label=\"$1\"\n local pattern=\"$2\"\n shift 2\n lo"
},
{
"path": "scripts/guards/task-status-cutover-audit.sh",
"chars": 2420,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nROOT_DIR=\"$(git rev-parse --show-toplevel)\"\ncd \"$ROOT_DIR\"\n\nFAILED=0\n\nprint_heade"
},
{
"path": "scripts/guards/task-submit-compensation-guard.mjs",
"chars": 2529,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\nimport { pathToFileURL } "
},
{
"path": "scripts/guards/task-target-states-no-polling-guard.mjs",
"chars": 3190,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\nimport process from 'process'\n\nconst root = process.cwd"
},
{
"path": "scripts/guards/test-behavior-quality-guard.mjs",
"chars": 2601,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\n\nconst root = process.cwd()\nconst targetDirs = [\n path"
},
{
"path": "scripts/guards/test-behavior-route-coverage-guard.mjs",
"chars": 1935,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\n\nconst root = process.cwd()\nconst catalogPath = path.jo"
},
{
"path": "scripts/guards/test-behavior-tasktype-coverage-guard.mjs",
"chars": 1722,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\n\nconst root = process.cwd()\nconst catalogPath = path.jo"
},
{
"path": "scripts/guards/test-route-coverage-guard.mjs",
"chars": 1847,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\n\nconst root = process.cwd()\nconst apiDir = path.join(ro"
},
{
"path": "scripts/guards/test-tasktype-coverage-guard.mjs",
"chars": 1750,
"preview": "#!/usr/bin/env node\n\nimport fs from 'fs'\nimport path from 'path'\n\nconst root = process.cwd()\nconst taskTypesPath = path."
},
{
"path": "scripts/media-archive-legacy-refs.ts",
"chars": 3831,
"preview": "import { logInfo as _ulogInfo, logError as _ulogError } from '@/lib/logging/core'\nimport { createHash } from 'node:crypt"
},
{
"path": "scripts/media-backfill-refs.ts",
"chars": 3530,
"preview": "import { logInfo as _ulogInfo, logError as _ulogError } from '@/lib/logging/core'\nimport { prisma } from '@/lib/prisma'\n"
},
{
"path": "scripts/media-build-unreferenced-index.ts",
"chars": 6276,
"preview": "import { logInfo as _ulogInfo, logError as _ulogError } from '@/lib/logging/core'\nimport { promises as fs } from 'node:f"
},
{
"path": "scripts/media-mapping.ts",
"chars": 2797,
"preview": "export type MediaFieldMapping = {\n legacyField: string\n mediaIdField: string\n}\n\nexport type MediaModelMapping = {\n mo"
},
{
"path": "scripts/media-restore-dry-run.ts",
"chars": 3812,
"preview": "import { logInfo as _ulogInfo, logError as _ulogError } from '@/lib/logging/core'\nimport { promises as fs } from 'node:f"
},
{
"path": "scripts/media-safety-backup.ts",
"chars": 7807,
"preview": "import { logInfo as _ulogInfo, logError as _ulogError } from '@/lib/logging/core'\nimport { createHash } from 'node:crypt"
},
{
"path": "scripts/migrate-cancelled-to-failed.ts",
"chars": 2043,
"preview": "import { prisma } from '@/lib/prisma'\n\nconst OLD_STATUS = 'cancelled'\nconst NEW_STATUS = 'failed'\nconst OLD_EVENT_TYPE ="
},
{
"path": "scripts/migrate-image-urls-contract.ts",
"chars": 5819,
"preview": "import { logInfo as _ulogInfo, logError as _ulogError } from '@/lib/logging/core'\nimport { prisma } from '@/lib/prisma'\n"
},
{
"path": "scripts/migrate-local-to-minio.ts",
"chars": 7341,
"preview": "#!/usr/bin/env npx tsx\n/**\n * 本地存储 → MinIO 迁移脚本\n * 使用 @aws-sdk/client-s3(项目已有依赖)\n * \n * 用法: npx tsx scripts/migrate-loca"
},
{
"path": "scripts/migrate-to-minio.sh",
"chars": 2534,
"preview": "#!/bin/bash\n#\n# 存储迁移快捷脚本\n# 用法: ./scripts/migrate-to-minio.sh [选项]\n#\n\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE["
},
{
"path": "scripts/migrate-to-minio.ts",
"chars": 10799,
"preview": "#!/usr/bin/env node\n/**\n * 存储迁移脚本: Local → MinIO\n * \n * 用途: 将本地文件存储的数据无缝迁移到 MinIO 对象存储\n * 特点:\n * - 断点续传(记录已迁移文件)\n * - 校验"
},
{
"path": "scripts/migrations/migrate-capability-selections.ts",
"chars": 8381,
"preview": "import { prisma } from '@/lib/prisma'\nimport {\n parseModelKeyStrict,\n type CapabilitySelections,\n type CapabilityValu"
},
{
"path": "scripts/migrations/migrate-custom-pricing-v2.ts",
"chars": 3894,
"preview": "import { prisma } from '@/lib/prisma'\n\nconst APPLY = process.argv.includes('--apply')\n\ntype PreferenceRow = {\n id: stri"
},
{
"path": "scripts/migrations/migrate-gateway-route-openai-compat.ts",
"chars": 2031,
"preview": "import { prisma } from '@/lib/prisma'\nimport { migrateGatewayRoutePayload } from '@/lib/migrations/gateway-route-openai-"
},
{
"path": "scripts/migrations/migrate-graph-artifacts-unique-index.ts",
"chars": 4062,
"preview": "import { prisma } from '@/lib/prisma'\n\nconst APPLY = process.argv.includes('--apply')\nconst REQUIRED_INDEX_NAME = 'graph"
}
]
// ... and 1130 more files (download for full content)
About this extraction
This page contains the full source code of the saturndec/waoowaoo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 1330 files (6.2 MB), approximately 1.7M tokens, and a symbol index with 5296 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.