P0 fixes:
- StorageType: UploadFromURL now returns actual storageType, fixing
GDrive tasks being incorrectly recorded as "s3"
- Concurrency: selectAccount returns ReleaseFunc to caller, fixing
premature slot release before task creation
- Race condition: applyRecoveredToken uses copy-on-write for
account.Credentials map to prevent concurrent write panic
- GetCameoStatus: use direct HTTP call to populate missing fields
(StatusMessage, InstructionSetHint, InstructionSet)
- Frontend: progress bar uses reactive now.value instead of
non-reactive Date.now()
P1 fixes:
- EditImage: pass req.Image through to CreateImageRequest
- ListPending: add LIMIT 200 to prevent unbounded queries
- Worker encapsulation: add ListPendingTasks/UpdateTask proxy methods
to SoraTaskService, worker no longer accesses repo directly
- Frontend polling: add retry counter (3 attempts) before giving up
P2-P3 fixes:
- QuotaBar: handle both quota_source and source field names
- i18n: replace hardcoded Chinese labels in SoraPromptBar and
example prompts in SoraGeneratePage with i18n keys
- Fix regenrate → regenerate i18n typo
- SoraMediaPreview: skip Escape handler when not visible
- SoraGeneratePage: void handleGenerate promise in handleRetry
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Define SoraObjectStorage interface abstracting S3 and GDrive backends
- Implement SoraGDriveStorage with OAuth2 and Service Account auth
- Add SoraStorageRouter to route requests based on active profile provider
- Add GDrive OAuth handler for authorization flow (start + callback)
- Extend profile model with provider, auth_type, and GDrive-specific fields
- Update frontend UI with provider selection, dynamic form fields, and i18n
- Migrate API paths from /sora-s3 to /sora-storage (old paths preserved)
- All existing S3 functionality remains backward compatible
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>