fix(*): 分镜生成设置 - 统一模型能力计价并支持项目默认模型

This commit is contained in:
ddCat
2026-08-30 13:02:55 +08:00
parent 2f818a8dde
commit f62ea62355
12 changed files with 342 additions and 68 deletions

View File

@@ -170,20 +170,22 @@ type CharacterVoiceBinding struct {
// Project 是短剧领域聚合根;CanvasProject 仍代表可独立创作的画布文档。
type Project struct {
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_projects_user_name,priority:1"`
Name string `json:"name" gorm:"size:240;uniqueIndex:idx_projects_user_name,priority:2"`
Type string `json:"type" gorm:"size:32;index"`
AspectRatio string `json:"aspectRatio" gorm:"size:16"`
SourceType string `json:"sourceType" gorm:"size:32"`
Description string `json:"description" gorm:"type:text"`
CoverResourceID string `json:"coverResourceId,omitempty" gorm:"index;size:36"`
StylePresetID string `json:"stylePresetId" gorm:"size:64"`
StyleProfileJSON string `json:"styleProfileJson" gorm:"type:text"`
Status ProjectStatus `json:"status" gorm:"index;size:24"`
Revision int64 `json:"revision"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" gorm:"index"`
ID string `json:"id" gorm:"primaryKey;size:36"`
UserID string `json:"userId" gorm:"index;size:36;uniqueIndex:idx_projects_user_name,priority:1"`
Name string `json:"name" gorm:"size:240;uniqueIndex:idx_projects_user_name,priority:2"`
Type string `json:"type" gorm:"size:32;index"`
AspectRatio string `json:"aspectRatio" gorm:"size:16"`
SourceType string `json:"sourceType" gorm:"size:32"`
Description string `json:"description" gorm:"type:text"`
CoverResourceID string `json:"coverResourceId,omitempty" gorm:"index;size:36"`
StylePresetID string `json:"stylePresetId" gorm:"size:64"`
StyleProfileJSON string `json:"styleProfileJson" gorm:"type:text"`
DefaultImageModel string `json:"defaultImageModel,omitempty" gorm:"size:500"`
DefaultVideoModel string `json:"defaultVideoModel,omitempty" gorm:"size:500"`
Status ProjectStatus `json:"status" gorm:"index;size:24"`
Revision int64 `json:"revision"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" gorm:"index"`
}
// StyleProfile 是用户可持续编辑的风格源;项目只保存应用当时的 JSON 快照,避免源对象更新污染历史生成。

View File

@@ -1131,6 +1131,7 @@ func (r *Repository) UpdateProject(project *model.Project) error {
"name": project.Name, "type": project.Type, "aspect_ratio": project.AspectRatio, "source_type": project.SourceType,
"description": project.Description, "cover_resource_id": project.CoverResourceID,
"style_preset_id": project.StylePresetID, "style_profile_json": project.StyleProfileJSON,
"default_image_model": project.DefaultImageModel, "default_video_model": project.DefaultVideoModel,
"status": project.Status, "revision": project.Revision, "updated_at": project.UpdatedAt,
}).Error
}

View File

@@ -14,25 +14,29 @@ import (
)
type CreateProjectRequest struct {
Name string `json:"name"`
Type string `json:"type"`
AspectRatio string `json:"aspectRatio"`
SourceType string `json:"sourceType"`
Description string `json:"description"`
StylePresetID string `json:"stylePresetId"`
StyleProfileJSON string `json:"styleProfileJson"`
Name string `json:"name"`
Type string `json:"type"`
AspectRatio string `json:"aspectRatio"`
SourceType string `json:"sourceType"`
Description string `json:"description"`
StylePresetID string `json:"stylePresetId"`
StyleProfileJSON string `json:"styleProfileJson"`
DefaultImageModel string `json:"defaultImageModel"`
DefaultVideoModel string `json:"defaultVideoModel"`
}
type UpdateProjectRequest struct {
Name string `json:"name"`
Type string `json:"type"`
AspectRatio string `json:"aspectRatio"`
SourceType string `json:"sourceType"`
Description *string `json:"description"`
CoverResourceID *string `json:"coverResourceId"`
StylePresetID *string `json:"stylePresetId"`
StyleProfileJSON *string `json:"styleProfileJson"`
Status string `json:"status"`
Name string `json:"name"`
Type string `json:"type"`
AspectRatio string `json:"aspectRatio"`
SourceType string `json:"sourceType"`
Description *string `json:"description"`
CoverResourceID *string `json:"coverResourceId"`
StylePresetID *string `json:"stylePresetId"`
StyleProfileJSON *string `json:"styleProfileJson"`
DefaultImageModel *string `json:"defaultImageModel"`
DefaultVideoModel *string `json:"defaultVideoModel"`
Status string `json:"status"`
}
type CreateProjectUnitRequest struct {
@@ -240,7 +244,15 @@ func (s *Service) CreateProject(userID string, req CreateProjectRequest) (model.
return model.Project{}, BadAuthRequest(err.Error())
}
now := time.Now()
project := model.Project{ID: newID(), UserID: userID, Name: name, Type: projectType, AspectRatio: aspectRatio, SourceType: sourceType, Description: strings.TrimSpace(req.Description), StylePresetID: stylePresetID, StyleProfileJSON: styleProfileJSON, Status: model.ProjectStatusActive, Revision: 1, CreatedAt: now, UpdatedAt: now}
defaultImageModel, err := normalizeProjectDefaultModel(req.DefaultImageModel)
if err != nil {
return model.Project{}, err
}
defaultVideoModel, err := normalizeProjectDefaultModel(req.DefaultVideoModel)
if err != nil {
return model.Project{}, err
}
project := model.Project{ID: newID(), UserID: userID, Name: name, Type: projectType, AspectRatio: aspectRatio, SourceType: sourceType, Description: strings.TrimSpace(req.Description), StylePresetID: stylePresetID, StyleProfileJSON: styleProfileJSON, DefaultImageModel: defaultImageModel, DefaultVideoModel: defaultVideoModel, Status: model.ProjectStatusActive, Revision: 1, CreatedAt: now, UpdatedAt: now}
if err := s.repo.CreateProject(&project); err != nil {
return model.Project{}, err
}
@@ -301,6 +313,20 @@ func (s *Service) UpdateProject(userID string, id string, req UpdateProjectReque
}
project.StyleProfileJSON = styleProfileJSON
}
if req.DefaultImageModel != nil {
defaultImageModel, modelErr := normalizeProjectDefaultModel(*req.DefaultImageModel)
if modelErr != nil {
return model.Project{}, modelErr
}
project.DefaultImageModel = defaultImageModel
}
if req.DefaultVideoModel != nil {
defaultVideoModel, modelErr := normalizeProjectDefaultModel(*req.DefaultVideoModel)
if modelErr != nil {
return model.Project{}, modelErr
}
project.DefaultVideoModel = defaultVideoModel
}
if err := validateStyleProfilePreset(project.StylePresetID, project.StyleProfileJSON); err != nil {
return model.Project{}, BadAuthRequest(err.Error())
}
@@ -318,6 +344,14 @@ func (s *Service) UpdateProject(userID string, id string, req UpdateProjectReque
return *project, nil
}
func normalizeProjectDefaultModel(value string) (string, error) {
modelRef := strings.TrimSpace(value)
if len(modelRef) > 500 {
return "", BadAuthRequest("项目默认模型标识过长")
}
return modelRef, nil
}
func (s *Service) DeleteProject(userID string, id string) error {
if _, err := s.repo.ProjectForUser(userID, id); err != nil {
return err

View File

@@ -82,6 +82,42 @@ func TestUpdateProjectCoverRequiresOwnedReadyImage(t *testing.T) {
}
}
func TestUpdateProjectPersistsDefaultGenerationModels(t *testing.T) {
service, db := newProjectSettingsTestService(t)
now := time.Now()
project := model.Project{ID: "project-model-defaults", UserID: "user-1", Name: "短剧", Status: model.ProjectStatusActive, Revision: 1, CreatedAt: now, UpdatedAt: now}
if err := db.Create(&project).Error; err != nil {
t.Fatal(err)
}
imageModel := " system-channel::image-model "
videoModel := "user-channel::MiniMax-H3"
updated, err := service.UpdateProject("user-1", project.ID, UpdateProjectRequest{DefaultImageModel: &imageModel, DefaultVideoModel: &videoModel})
if err != nil {
t.Fatal(err)
}
if updated.DefaultImageModel != "system-channel::image-model" || updated.DefaultVideoModel != videoModel {
t.Fatalf("updated model defaults = %q / %q", updated.DefaultImageModel, updated.DefaultVideoModel)
}
var persisted model.Project
if err := db.First(&persisted, "id = ?", project.ID).Error; err != nil {
t.Fatal(err)
}
if persisted.DefaultImageModel != updated.DefaultImageModel || persisted.DefaultVideoModel != updated.DefaultVideoModel {
t.Fatalf("persisted model defaults = %q / %q", persisted.DefaultImageModel, persisted.DefaultVideoModel)
}
empty := ""
cleared, err := service.UpdateProject("user-1", project.ID, UpdateProjectRequest{DefaultVideoModel: &empty})
if err != nil {
t.Fatal(err)
}
if cleared.DefaultVideoModel != "" {
t.Fatalf("default video model = %q after clear", cleared.DefaultVideoModel)
}
}
func TestProjectUnitSummaryPersistsWordCount(t *testing.T) {
service, db := newProjectSettingsTestService(t)
now := time.Now()