From 735eb69e354250c2bebd0326aedc32ec55c6acd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Mon, 27 Jul 2026 09:34:02 +0800 Subject: [PATCH] 1 --- config.yml | 4 +- prompt.md | 15 +++-- short_drama.db | Bin 462848 -> 462848 bytes shortdrama/dao/account_transaction_dao.go | 10 +--- shortdrama/dao/agent_profile_dao.go | 4 +- shortdrama/dao/customer_profile_dao.go | 4 +- shortdrama/dao/episode_dao.go | 25 ++------- shortdrama/dao/model_config_dao.go | 20 ++++++- shortdrama/dao/region_pricing_dao.go | 4 +- shortdrama/dao/user_model_config_dao.go | 8 ++- shortdrama/model/domain/shot.go | 43 +++++++------- .../model/dto/account_transaction_dto.go | 2 +- shortdrama/model/dto/agent_profile_dto.go | 2 +- shortdrama/model/dto/customer_profile_dto.go | 2 +- shortdrama/model/dto/drama_dto.go | 4 +- shortdrama/model/dto/model_config_dto.go | 2 +- shortdrama/model/dto/region_pricing_dto.go | 2 +- shortdrama/model/dto/user_model_config_dto.go | 2 +- shortdrama/service/episode_service.go | 4 +- .../service/user_model_config_service.go | 53 +++++++++++------- 20 files changed, 114 insertions(+), 96 deletions(-) diff --git a/config.yml b/config.yml index 51c4a2a..bac188f 100644 --- a/config.yml +++ b/config.yml @@ -10,11 +10,11 @@ server: name: video workerId: 1 clientMaxBodySize: 209715200 # 200MB 上传文件限制 - requestTimeout: 500 # HTTP请求超时(秒),默认60秒,改为3分钟以支持AI生成脚本 + requestTimeout: 3000 # HTTP请求超时(秒),默认60秒,改为3分钟以支持AI生成脚本 # AI模型调用配置 chat: - timeout: 500 # 对话模型API请求超时时间(秒) + timeout: 3000 # 对话模型API请求超时时间(秒) max_retries: 3 # 请求失败最大重试次数 # 各内容类型的画风/镜间质量/防崩坏约束及镜头时长规则(生成脚本时注入 prompt) diff --git a/prompt.md b/prompt.md index 1864593..22a4aa7 100644 --- a/prompt.md +++ b/prompt.md @@ -75,19 +75,21 @@ - **丰富而非删减**:在保留全部原有细节的基础上,可以增加合理的微观动作、反应表情、环境互动来让每个镜头更丰满。不要删除任何已有内容来"精简"。 - **禁止概括**:如果你发现自己把一个具体的动作写成了概括性的描述,说明你在删减。请回到剧情描述中把具体细节还原出来。 -### 三大字段的严格区分 +### 四大字段的严格区分 -每个镜头的 JSON 中有三个文本字段:`event`(画面描述)、`narration`(旁白配音)、`dialogue`(角色台词)。**它们的用途完全不同,严禁混用:** +每个镜头的 JSON 中有四个文本字段:`event`(画面描述)、`dialogue`(角色台词)、`narration`(旁白配音)、`ambientSound`(环境音效)。**它们的用途完全不同,严禁混用:** | 字段 | 用途 | 是否会配音 | 示例 | |------|------|-----------|------| | `event` | **画面描述**——观众在屏幕上直接看到的一切:场景环境、角色动作、表情变化、物体位置等。这是视频模型生成画面的唯一依据,必须详细准确。 | 否 | "他落在夜市街上,荧光冲锋衣在人群里格外扎眼" | -| `narration` | **旁白配音**——需要配音演员念出来的解说文字,如角色内心独白、故事背景交代、时间跳跃说明等。**必须是不能通过画面直接传达的信息。** | 是 | "虾仁万万没想到,一个外卖竟然改变了他的一生" | | `dialogue` | **角色台词**——角色在画面中亲口说出的对白。多人对话用「角色名:台词」格式。 | 是 | "拿下" | +| `narration` | **旁白配音**——需要配音演员念出来的解说文字,如角色内心独白、故事背景交代、时间跳跃说明等。**必须是不能通过画面直接传达的信息。** | 是 | "虾仁万万没想到,一个外卖竟然改变了他的一生" | +| `ambientSound` | **环境音效**——背景中的声音元素,如风声、雨声、脚步声、警笛声、人群嘈杂、门铃声、音乐等。**无需配音演员,后期音效合成。** | 否(音效) | "夜市人声嘈杂,远处传来救护车的鸣笛声" | **核心判断方法:** - 如果这段文字描述的是观众能直接看到的画面内容(谁做了什么、在哪里、环境什么样)→ 放入 `event`,**严禁放入 `narration`** - 只有在"画面上看不到这段内容,但需要配音解说才能让观众理解"时 → 才放入 `narration` +- 如果描述的是背景中的声音元素(观众听到但画面中无人提及)→ 放入 `ambientSound` **常见错误**(禁止): ``` @@ -99,10 +101,15 @@ narration: "他落在夜市街上" ← 画面描述不应是旁白 event: "他落在夜市街上" ← 同一内容写两遍 +错误3:把环境音混在事件描述中 + event: "夜市人声嘈杂,他落在夜市街上" ← 画面内容归event,环境音单独分离 + ambientSound: "夜市人声嘈杂" ← 背景音效应放入ambientSound + 正确示范: event: "他落在夜市街上,荧光冲锋衣在人群里格外扎眼" ← 画面描述归event - narration: "" ← 纯视觉内容不需要配音旁白 dialogue: "" ← 本镜头无人说话 + narration: "" ← 纯视觉内容不需要配音旁白 + ambientSound: "夜市人声嘈杂" ← 背景音效归ambientSound ``` **短剧需要旁白解说和角色对白来推动剧情,但旁白必须是有配音价值的内容(角色内心想法、背景说明等),不能把画面描述放进旁白凑字数。** diff --git a/short_drama.db b/short_drama.db index 38788e30926a89c163e6e167d99a87728e6473b3..214801af4841286b74f7b170a97ce8233c58feda 100644 GIT binary patch delta 7746 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 i^H)MZ&fc5+^S;g!#9cNN01Y;f3gw^x delta 5156 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 0 { m = m.Where("cp.agent_id", agentId) diff --git a/shortdrama/dao/episode_dao.go b/shortdrama/dao/episode_dao.go index 6c2beac..c8cfa89 100644 --- a/shortdrama/dao/episode_dao.go +++ b/shortdrama/dao/episode_dao.go @@ -6,7 +6,6 @@ import ( "video-factory/shortdrama/consts" "video-factory/shortdrama/model/entity" - "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gcache" "github.com/gogf/gf/v2/util/gconv" @@ -52,16 +51,14 @@ func init() { g.Log().Debugf(ctx, "添加 description 列失败(可能已存在): %v", err) } } -func clearEpisodeCache(ctx context.Context, id int64, dramaId int64) { +func clearEpisodeCache(ctx context.Context, id int64) { _, _ = gcache.Remove(ctx, consts.TableNameEpisode+"_GetOneByPk_"+gconv.String(id)) - _, _ = gcache.Remove(ctx, "episode_ListPageByDrama_"+gconv.String(dramaId)) - _, _ = gcache.Remove(ctx, "episode_GetMaxIndex_"+gconv.String(dramaId)) } func (d *episodeDao) Insert(ctx context.Context, data *entity.Episode) (int64, error) { id, err := common.InsertAndReturnId(ctx, consts.TableNameEpisode, data) if err == nil { - clearEpisodeCache(ctx, id, data.DramaId) + clearEpisodeCache(ctx, id) } return id, err } @@ -82,8 +79,6 @@ func (d *episodeDao) Delete(ctx context.Context, id int64) error { func (d *episodeDao) DeleteByDrama(ctx context.Context, dramaId int64) error { _, err := g.DB().Model(consts.TableNameEpisode).Ctx(ctx).Unscoped().Where("drama_id", dramaId).Delete() - _, _ = gcache.Remove(ctx, "episode_ListPageByDrama_"+gconv.String(dramaId)) - _, _ = gcache.Remove(ctx, "episode_GetMaxIndex_"+gconv.String(dramaId)) return err } @@ -103,11 +98,7 @@ func (d *episodeDao) ListPageByDrama(ctx context.Context, dramaId int64, page, p m = m.Where("title LIKE ?", "%"+keyword[0]+"%") } if pageSize == -1 { - cacheKey := "episode_ListPageByDrama_" + gconv.String(dramaId) - if len(keyword) > 0 && keyword[0] != "" { - cacheKey += "_" + keyword[0] - } - r, err := m.OrderAsc("idx").Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: cacheKey}).All() + r, err := m.OrderAsc("idx").All() if err != nil { return nil, 0, err } @@ -121,11 +112,7 @@ func (d *episodeDao) ListPageByDrama(ctx context.Context, dramaId int64, page, p if pageSize < 1 { pageSize = 10 } - cacheKey := "episode_ListPageByDrama_" + gconv.String(dramaId) - if len(keyword) > 0 && keyword[0] != "" { - cacheKey += "_" + keyword[0] - } - r, total, err := m.OrderAsc("idx").Limit(pageSize).Offset((page - 1) * pageSize).Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: cacheKey}).AllAndCount(false) + r, total, err := m.OrderAsc("idx").Limit(pageSize).Offset((page - 1) * pageSize).AllAndCount(false) if err != nil { return } @@ -135,9 +122,7 @@ func (d *episodeDao) ListPageByDrama(ctx context.Context, dramaId int64, page, p } func (d *episodeDao) GetMaxIndex(ctx context.Context, dramaId int64) (int, error) { - r, err := g.DB().Model(consts.TableNameEpisode).Ctx(ctx). - Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: "episode_GetMaxIndex_" + gconv.String(dramaId)}). - Where("drama_id", dramaId).OrderDesc("idx").Value("idx") + r, err := g.DB().Model(consts.TableNameEpisode).Ctx(ctx).Where("drama_id", dramaId).OrderDesc("idx").Value("idx") if err != nil { return 0, err } diff --git a/shortdrama/dao/model_config_dao.go b/shortdrama/dao/model_config_dao.go index 5494e97..ba61775 100644 --- a/shortdrama/dao/model_config_dao.go +++ b/shortdrama/dao/model_config_dao.go @@ -224,7 +224,7 @@ func (d *modelConfigDao) ListPage(ctx context.Context, page, pageSize int, model if keyword != "" { cacheKey += "_" + keyword } - r, err := m.OrderAsc("id").Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: cacheKey}).All() + r, err := m.OrderAsc("id").All() if err != nil { return nil, 0, err } @@ -245,7 +245,7 @@ func (d *modelConfigDao) ListPage(ctx context.Context, page, pageSize int, model if keyword != "" { cacheKey += "_" + keyword } - r, total, err := m.OrderAsc("id").Limit(pageSize).Offset((page - 1) * pageSize).Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: cacheKey}).AllAndCount(false) + r, total, err := m.OrderAsc("id").Limit(pageSize).Offset((page - 1) * pageSize).AllAndCount(false) if err != nil { return } @@ -284,6 +284,22 @@ func (d *modelConfigDao) GetActiveModelByType(ctx context.Context, modelType str return } +func (d *modelConfigDao) GetByIds(ctx context.Context, ids []int64) (res []*entity.ModelConfig, err error) { + if len(ids) == 0 { + return make([]*entity.ModelConfig, 0), nil + } + r, err := g.DB().Model(consts.TableNameModelConfig).Ctx(ctx). + WhereIn("id", ids).OrderAsc("id").All() + if err != nil { + return nil, err + } + res = make([]*entity.ModelConfig, 0) + if r != nil { + err = r.Structs(&res) + } + return +} + func (d *modelConfigDao) GetAll(ctx context.Context) (res []*entity.ModelConfig, err error) { r, err := g.DB().Model(consts.TableNameModelConfig).Ctx(ctx). Cache(gdb.CacheOption{Duration: time.Minute, Name: "model_config_GetAll"}). diff --git a/shortdrama/dao/region_pricing_dao.go b/shortdrama/dao/region_pricing_dao.go index a6e1abf..87f9110 100644 --- a/shortdrama/dao/region_pricing_dao.go +++ b/shortdrama/dao/region_pricing_dao.go @@ -87,7 +87,7 @@ func (d *regionPricingDao) ListPage(ctx context.Context, page, pageSize int, key if protected == 0 || protected == 1 { cacheKey += "_protected_" + gconv.String(protected) } - r, err := m.OrderAsc("region, protected").Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: cacheKey}).All() + r, err := m.OrderAsc("region, protected").All() if err != nil { return nil, 0, err } @@ -108,7 +108,7 @@ func (d *regionPricingDao) ListPage(ctx context.Context, page, pageSize int, key if protected == 0 || protected == 1 { cacheKey += "_protected_" + gconv.String(protected) } - r, total, err := m.OrderAsc("region, protected").Limit(pageSize).Offset((page - 1) * pageSize).Cache(gdb.CacheOption{Duration: common.CacheTTL(), Name: cacheKey}).AllAndCount(false) + r, total, err := m.OrderAsc("region, protected").Limit(pageSize).Offset((page - 1) * pageSize).AllAndCount(false) if err != nil { return } diff --git a/shortdrama/dao/user_model_config_dao.go b/shortdrama/dao/user_model_config_dao.go index ae29d97..fe4bc71 100644 --- a/shortdrama/dao/user_model_config_dao.go +++ b/shortdrama/dao/user_model_config_dao.go @@ -223,7 +223,7 @@ func (d *userModelConfigDao) Save(ctx context.Context, data *entity.UserModelCon // SaveBatch 批量保存用户模型配置(事务内执行) func (d *userModelConfigDao) SaveBatch(ctx context.Context, items []*entity.UserModelConfig) error { - return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { for _, data := range items { m := gconv.Map(data, gconv.MapOption{Tags: []string{"orm"}}) delete(m, "id") @@ -242,6 +242,12 @@ func (d *userModelConfigDao) SaveBatch(ctx context.Context, items []*entity.User } return nil }) + if err == nil { + for _, item := range items { + clearUserModelConfigCache(ctx, item.UserId, item.ModelType, item.ModelConfigId) + } + } + return err } // clearUserModelConfigCache 清除用户模型配置的缓存 diff --git a/shortdrama/model/domain/shot.go b/shortdrama/model/domain/shot.go index ebde1b3..6c7c051 100644 --- a/shortdrama/model/domain/shot.go +++ b/shortdrama/model/domain/shot.go @@ -9,16 +9,17 @@ import ( // Shot 单集剧本中的单个镜头 type Shot struct { Index int `json:"index"` - StartTime string `json:"startTime"` // "MM:SS" - EndTime string `json:"endTime"` // "MM:SS" - Event string `json:"event"` // 事件描述/动作描写 - Narration string `json:"narration,omitempty"` // 旁白(画外音解说,不是角色说的话) - Dialogue string `json:"dialogue,omitempty"` // 主台词(角色亲口说的对白) - CameraMovement string `json:"cameraMovement"` // 运镜描述 - ShotSize string `json:"shotSize"` // 景别(远景/全景/中景/近景/特写) - Characters []string `json:"characters"` // 出演人物(演员名列表) - Scene string `json:"scene"` // 场景(场景名) - Props []string `json:"props"` // 道具(道具名列表) + StartTime string `json:"startTime"` // "MM:SS" + EndTime string `json:"endTime"` // "MM:SS" + Event string `json:"event"` // 事件描述/动作描写 + Narration string `json:"narration,omitempty"` // 旁白/画外音(需要配音的解说文字) + Dialogue string `json:"dialogue,omitempty"` // 主台词(角色亲口说的对白) + AmbientSound string `json:"ambientSound,omitempty"` // 环境音(背景音效,如风声、脚步声、警笛声等) + CameraMovement string `json:"cameraMovement"` // 运镜描述 + ShotSize string `json:"shotSize"` // 景别(远景/全景/中景/近景/特写) + Characters []string `json:"characters"` // 出演人物(演员名列表) + Scene string `json:"scene"` // 场景(场景名) + Props []string `json:"props"` // 道具(道具名列表) } // ShotsToText 将镜头数组转回纯文本格式,供视频生成流程(ReAct Agent)使用 @@ -29,28 +30,24 @@ func ShotsToText(shots []Shot) string { } for _, s := range shots { fmt.Fprintf(&b, "【镜头%d】(%s-%s)\n", s.Index, s.StartTime, s.EndTime) + if s.Event != "" { + fmt.Fprintf(&b, "事件:%s\n", s.Event) + } if s.Dialogue != "" { fmt.Fprintf(&b, "台词:%s\n", s.Dialogue) } - // event 是视频模型的画面依据,把 narration 内容合并到 event 中, - // 防止 AI 误把画面描述写进 narration 导致视频模型缺少画面依据 - eventText := s.Event if s.Narration != "" { - if eventText != "" { - eventText = s.Narration + "。" + eventText - } else { - eventText = s.Narration - } + fmt.Fprintf(&b, "画外音:%s\n", s.Narration) } - if eventText != "" { - fmt.Fprintf(&b, "事件:%s\n", eventText) - } - if s.CameraMovement != "" { - fmt.Fprintf(&b, "运镜:%s\n", s.CameraMovement) + if s.AmbientSound != "" { + fmt.Fprintf(&b, "环境音:%s\n", s.AmbientSound) } if s.ShotSize != "" { fmt.Fprintf(&b, "景别:%s\n", s.ShotSize) } + if s.CameraMovement != "" { + fmt.Fprintf(&b, "运镜:%s\n", s.CameraMovement) + } b.WriteString("\n") } return strings.TrimRight(b.String(), "\n") diff --git a/shortdrama/model/dto/account_transaction_dto.go b/shortdrama/model/dto/account_transaction_dto.go index 32a3d54..c549137 100644 --- a/shortdrama/model/dto/account_transaction_dto.go +++ b/shortdrama/model/dto/account_transaction_dto.go @@ -17,7 +17,7 @@ type ListTransactionReq struct { UserId int64 `v:"required" json:"user_id"` Type string `json:"type" v:"in:recharge,deduct" dc:"筛选类型:recharge/deduct"` Page int `json:"page" d:"1" v:"min:1" dc:"页码"` - PageSize int `json:"page_size" d:"20" v:"min:1|max:100" dc:"每页条数"` + PageSize int `json:"page_size" d:"20" v:"min:-1" dc:"每页条数"` } type ListTransactionRes struct { diff --git a/shortdrama/model/dto/agent_profile_dto.go b/shortdrama/model/dto/agent_profile_dto.go index f9c708e..b2334ee 100644 --- a/shortdrama/model/dto/agent_profile_dto.go +++ b/shortdrama/model/dto/agent_profile_dto.go @@ -20,7 +20,7 @@ type CreateAgentReq struct { type ListAgentReq struct { g.Meta `path:"/list" method:"get" tags:"短剧管理" summary:"代理商列表"` Page int `json:"page" d:"1" v:"min:1" dc:"页码"` - PageSize int `json:"page_size" d:"20" v:"min:1|max:100" dc:"每页条数"` + PageSize int `json:"page_size" d:"20" v:"min:-1" dc:"每页条数"` Keyword string `json:"keyword" dc:"搜索关键词"` Phone string `json:"phone" dc:"手机号"` Province string `json:"province" dc:"省份"` diff --git a/shortdrama/model/dto/customer_profile_dto.go b/shortdrama/model/dto/customer_profile_dto.go index a21662e..e2b6c25 100644 --- a/shortdrama/model/dto/customer_profile_dto.go +++ b/shortdrama/model/dto/customer_profile_dto.go @@ -18,7 +18,7 @@ type ListCustomerReq struct { g.Meta `path:"/list" method:"get" tags:"短剧管理" summary:"客户列表"` AgentId int64 `json:"agent_id" dc:"代理商ID"` Page int `json:"page" d:"1" v:"min:1" dc:"页码"` - PageSize int `json:"page_size" d:"20" v:"min:1|max:100" dc:"每页条数"` + PageSize int `json:"page_size" d:"20" v:"min:-1" dc:"每页条数"` Keyword string `json:"keyword" dc:"搜索关键词"` Phone string `json:"phone" dc:"手机号"` Province string `json:"province" dc:"省份"` diff --git a/shortdrama/model/dto/drama_dto.go b/shortdrama/model/dto/drama_dto.go index 5e04234..bc3cc0d 100644 --- a/shortdrama/model/dto/drama_dto.go +++ b/shortdrama/model/dto/drama_dto.go @@ -12,7 +12,7 @@ import ( type ListDramaReq struct { g.Meta `path:"/list" method:"get" tags:"短剧管理" summary:"短剧列表"` Page int `json:"page" d:"1" v:"min:1" dc:"页码,从1开始"` - PageSize int `json:"pageSize" d:"20" v:"min:1|max:100" dc:"每页条数"` + PageSize int `json:"pageSize" d:"20" v:"min:-1" dc:"每页条数"` Keyword string `json:"keyword" dc:"搜索关键词"` } @@ -29,7 +29,7 @@ type ListEpisodeReq struct { g.Meta `path:"/list" method:"get" tags:"短剧管理" summary:"剧集列表"` DramaId int64 `v:"required" json:"dramaId" dc:"短剧ID"` Page int `json:"page" d:"1" v:"min:1" dc:"页码,从1开始"` - PageSize int `json:"pageSize" d:"20" v:"min:1|max:100" dc:"每页条数"` + PageSize int `json:"pageSize" d:"20" v:"min:-1" dc:"每页条数"` Keyword string `json:"keyword" dc:"搜索关键词"` } diff --git a/shortdrama/model/dto/model_config_dto.go b/shortdrama/model/dto/model_config_dto.go index ec3a1f4..cfd098b 100644 --- a/shortdrama/model/dto/model_config_dto.go +++ b/shortdrama/model/dto/model_config_dto.go @@ -11,7 +11,7 @@ import ( type GetModelConfigListReq struct { g.Meta `path:"/get-model-list" method:"get" tags:"模型配置" summary:"获取模型配置列表"` Page int `json:"page" d:"1" v:"min:1" dc:"页码,默认1"` - PageSize int `json:"pageSize" d:"20" v:"min:1|max:100" dc:"每页条数,默认20"` + PageSize int `json:"pageSize" d:"20" v:"min:-1" dc:"每页条数,默认20"` ModelType string `json:"modelType" v:"in:chat,video" dc:"模型类型筛选: chat/video"` Keyword string `json:"keyword" dc:"模型名称关键词"` } diff --git a/shortdrama/model/dto/region_pricing_dto.go b/shortdrama/model/dto/region_pricing_dto.go index 27edcf8..6c8848a 100644 --- a/shortdrama/model/dto/region_pricing_dto.go +++ b/shortdrama/model/dto/region_pricing_dto.go @@ -9,7 +9,7 @@ import ( type ListRegionPricingReq struct { g.Meta `path:"/list" method:"get" tags:"短剧管理" summary:"地区定价列表"` Page int `json:"page" d:"1" v:"min:1" dc:"页码,默认1"` - PageSize int `json:"pageSize" d:"20" v:"min:1|max:100" dc:"每页条数,默认20"` + PageSize int `json:"pageSize" d:"20" v:"min:-1" dc:"每页条数,默认20"` Keyword string `json:"keyword" dc:"地区名称关键词"` Protected int `json:"protected" d:"-1" v:"in:-1,0,1" dc:"类型筛选: -1=全部 0=普通 1=受保护"` } diff --git a/shortdrama/model/dto/user_model_config_dto.go b/shortdrama/model/dto/user_model_config_dto.go index 7681dbc..d988d1f 100644 --- a/shortdrama/model/dto/user_model_config_dto.go +++ b/shortdrama/model/dto/user_model_config_dto.go @@ -36,7 +36,7 @@ type SaveUserModelConfigReq struct { type GetUserModelListReq struct { g.Meta `path:"/get-user-model-list" method:"get" tags:"模型配置" summary:"获取系统模型列表及用户配置状态"` Page int `json:"page" d:"1" v:"min:1" dc:"页码,默认1"` - PageSize int `json:"pageSize" d:"20" v:"min:1|max:100" dc:"每页条数,默认20"` + PageSize int `json:"pageSize" d:"20" v:"min:-1" dc:"每页条数,默认20"` ModelType string `json:"modelType" v:"in:chat,video" dc:"模型类型筛选: chat/video"` Keyword string `json:"keyword" dc:"模型名称关键词"` } diff --git a/shortdrama/service/episode_service.go b/shortdrama/service/episode_service.go index 128590c..63d56e2 100644 --- a/shortdrama/service/episode_service.go +++ b/shortdrama/service/episode_service.go @@ -350,9 +350,9 @@ func (s *episodeService) buildScriptGenUserInput(ctx context.Context, d *entity. b.WriteString("\n") } - b.WriteString("【输出格式】\nJSON数组,每个元素是一个镜头对象(shot),包含以下字段:\n- index: 镜头序号(从1开始)\n- startTime: 开始时间(格式MM:SS)\n- endTime: 结束时间(格式MM:SS)\n- event: 画面描述——观众在屏幕上直接看到的一切。场景环境、角色动作、表情变化、物体位置等。**这是视频模型生成画面的唯一依据,所有画面内容必须写在这里**,严禁把画面描述放入 narration 字段。不包含旁白和台词。\n- dialogue: 主台词——角色在画面中亲口说出的对白。多人对话用「角色名:台词」格式。旁白和内心独白不属于这里。如果本镜头无人说话,留空即可。\n- narration: 旁白配音——需要配音演员念出来的解说文字。必须是**通过画面无法直接传达**的信息(如角色内心独白、故事背景交代、时间跳跃说明等)。**如果一段文字描述的是观众能直接看到的画面内容,它就不属于旁白,必须放进 event 字段。** 本镜头无旁白时留空即可。\n- cameraMovement: 运镜方式,从以下标准类型中选择一种:固定镜头、推、拉、摇、移、跟、升、降、旋转、晃动、航拍\n- shotSize: 景别,从以下标准类型中选择一种:远景、全景、中景、近景、特写\n- characters: 出演人物数组,填写演员名称,如[\"张三\", \"李四\"],从「可用演员」中选择\n- scene: 场景名称,从「可用场景」中选择\n- props: 道具名称数组,如[\"剑\", \"酒杯\"],从「可用道具」中选择\n\n直接输出JSON数组,不要markdown代码块标记,不要其他任何内容。\n\n") + b.WriteString("【输出格式】\nJSON数组,每个元素是一个镜头对象(shot),包含以下字段:\n- index: 镜头序号(从1开始)\n- startTime: 开始时间(格式MM:SS)\n- endTime: 结束时间(格式MM:SS)\n- event: 画面描述——观众在屏幕上直接看到的一切。场景环境、角色动作、表情变化、物体位置等。**这是视频模型生成画面的唯一依据,所有画面内容必须写在这里**,严禁把画面描述放入 narration 字段。不包含旁白和台词。\n- dialogue: 主台词——角色在画面中亲口说出的对白。多人对话用「角色名:台词」格式。旁白和内心独白不属于这里。如果本镜头无人说话,留空即可。\n- narration: 旁白配音——需要配音演员念出来的解说文字。必须是**通过画面无法直接传达**的信息(如角色内心独白、故事背景交代、时间跳跃说明等)。**如果一段文字描述的是观众能直接看到的画面内容,它就不属于旁白,必须放进 event 字段。** 本镜头无旁白时留空即可。\n- ambientSound: 环境音效——背景中的声音元素,如风声、雨声、脚步声、警笛声、门铃声、人群嘈杂等。后期音效合成,不需要配音。本镜头无特殊环境音时留空即可。\n- shotSize: 景别,从以下标准类型中选择一种:远景、全景、中景、近景、特写\n- cameraMovement: 运镜方式,从以下标准类型中选择一种:固定镜头、推、拉、摇、移、跟、升、降、旋转、晃动、航拍\n- characters: 出演人物数组,填写演员名称,如[\"张三\", \"李四\"],从「可用演员」中选择\n- scene: 场景名称,从「可用场景」中选择\n- props: 道具名称数组,如[\"剑\", \"酒杯\"],从「可用道具」中选择\n\n直接输出JSON数组,不要markdown代码块标记,不要其他任何内容。\n\n") - b.WriteString("【创作规范】\n\n1. 剧情完整性【核心规则】—— 剧情描述是唯一创作依据,严格按每一句话、每一个细节逐拍还原。\n - 剧情描述中出现的每一句角色说的话,必须一字不差地写入对应镜头的dialogue字段。严禁把台词\"翻译\"成事件描述。\n - 剧情描述中的每一个具体细节(人物的动作、表情反应、对话、环境互动等)都必须忠实地保留在对应镜头的event、narration或dialogue字段中,不能省略、概括或改写为泛化描述。\n - 所有镜头时长之和应等于 %d 秒(50秒至少20个镜头,20-25个镜头为最佳密度)。\n\n2. 场景一致性 —— 每个镜头的 scene 字段值必须与 event 中画面描述的地点一致。\n - 如果 event 描述角色在\"实验室内\",scene 必须是该实验室的名称,不能写成其他地点。\n - 如果角色从一个场景移动到另一个场景,必须先切换 scene(如从「街道」切到「室内」),再描述新地点中的动作。\n\n3. 景别与动作匹配规则 —— 按镜头内容选择正确的 shotSize:\n - shotSize=特写:面部表情、微表情、眼神变化、细节反应(手部小动作、物品细节)。不能用于动作展示。\n - shotSize=近景:对话、上半身互动、情绪交流。能看清面部表情和手势。\n - shotSize=中景:日常动作、手持物体、多人交互。膝盖以上,既有动作又有空间感。\n - shotSize=全景:连贯动作、奔跑、打斗、空间关系。展示全身动作和环境关系。\n - shotSize=远景:环境交代、空镜过渡、场景建立。没有人或人很小,用于转场。\n\n4. 运镜搭配规则 —— 按场景类型选择 cameraMovement:\n - 对话/情绪交流 → 固定镜头或缓推\n - 动作/打斗 → 跟、摇、移\n - 人物进入/离开画面 → 跟、移\n - 情绪揭示 → 推\n - 环境展示 → 摇、升降、航拍\n - 紧张/混乱 → 晃动\n - 固定镜头是默认选项,无特殊需要不使用复杂运镜。\n\n5. 口型适配规则 —— 有台词的镜头(dialogue 不为空)必须遵守:\n - shotSize 必须为中景或近景(不能是远景、全景或特写)。\n - 说话角色必须面向或侧向镜头,不能背对镜头说话。\n - 不说话只做反应的角色可用其他景别。\n") + b.WriteString("【创作规范】\n\n1. 剧情完整性【核心规则】—— 剧情描述是唯一创作依据,严格按每一句话、每一个细节逐拍还原。\n - 剧情描述中出现的每一句角色说的话,必须一字不差地写入对应镜头的dialogue字段。严禁把台词\"翻译\"成事件描述。\n - 剧情描述中的每一个具体细节(人物的动作、表情反应、对话、环境互动等)都必须忠实地保留在对应镜头的event、dialogue或narration字段中,不能省略、概括或改写为泛化描述。\n - 所有镜头时长之和应等于 %d 秒(50秒至少20个镜头,20-25个镜头为最佳密度)。\n\n2. 场景一致性 —— 每个镜头的 scene 字段值必须与 event 中画面描述的地点一致。\n - 如果 event 描述角色在\"实验室内\",scene 必须是该实验室的名称,不能写成其他地点。\n - 如果角色从一个场景移动到另一个场景,必须先切换 scene(如从「街道」切到「室内」),再描述新地点中的动作。\n\n3. 景别与动作匹配规则 —— 按镜头内容选择正确的 shotSize:\n - shotSize=特写:面部表情、微表情、眼神变化、细节反应(手部小动作、物品细节)。不能用于动作展示。\n - shotSize=近景:对话、上半身互动、情绪交流。能看清面部表情和手势。\n - shotSize=中景:日常动作、手持物体、多人交互。膝盖以上,既有动作又有空间感。\n - shotSize=全景:连贯动作、奔跑、打斗、空间关系。展示全身动作和环境关系。\n - shotSize=远景:环境交代、空镜过渡、场景建立。没有人或人很小,用于转场。\n\n4. 运镜搭配规则 —— 按场景类型选择 cameraMovement:\n - 对话/情绪交流 → 固定镜头或缓推\n - 动作/打斗 → 跟、摇、移\n - 人物进入/离开画面 → 跟、移\n - 情绪揭示 → 推\n - 环境展示 → 摇、升降、航拍\n - 紧张/混乱 → 晃动\n - 固定镜头是默认选项,无特殊需要不使用复杂运镜。\n\n5. 口型适配规则 —— 有台词的镜头(dialogue 不为空)必须遵守:\n - shotSize 必须为中景或近景(不能是远景、全景或特写)。\n - 说话角色必须面向或侧向镜头,不能背对镜头说话。\n - 不说话只做反应的角色可用其他景别。\n") b.WriteString(agent.GetShotDurationPrompt(ctx, d.Type)) b.WriteString(agent.GetStylePrompt(ctx, d.Type)) b.WriteString("\n\n6. JSON格式要求:输出必须是合法的 JSON 数组,字符串值中若需使用引号请用「」代替 ASCII 双引号,确保 json.Unmarshal 能正确解析。\n7. 风控规避:视频模型对某些画面有安全审查机制,生成镜头描述时必须规避。政治敏感内容(真实政治人物/标志/当代军警标识)零容忍。暴力血腥画面(刀刺入身体、血溅、断肢)改为反应镜头或切镜回避,不展示伤害过程。当代背景中的军警抓捕动作将「警察/武警」改为「保安/黑衣人」,动作从「按倒铐住」改为「围住请离」。武器描写聚焦持武器的人物(「他握紧长刀,目光凌厉」),不写武器接触身体的画面。暴力结果用间接描写:写周围人反应而非伤口血迹。\n") diff --git a/shortdrama/service/user_model_config_service.go b/shortdrama/service/user_model_config_service.go index 49a99ae..84de4e2 100644 --- a/shortdrama/service/user_model_config_service.go +++ b/shortdrama/service/user_model_config_service.go @@ -190,15 +190,23 @@ func (s *userModelConfigService) GetMergedConfig(ctx context.Context, userId int } func (s *userModelConfigService) GetUserModelList(ctx context.Context, userId int64) *dto.GetUserModelListRes { - models := ModelConfigService.getModelList(ctx) - list := make([]*dto.UserModelItem, 0, len(models)) - userCfgs, _ := dao.UserModelConfig.GetByUserId(ctx, userId) - cfgMap := make(map[int64]*entity.UserModelConfig) - for _, uc := range userCfgs { - cfgMap[uc.ModelConfigId] = uc + if len(userCfgs) == 0 { + return &dto.GetUserModelListRes{List: make([]*dto.UserModelItem, 0)} } - + ids := make([]int64, 0, len(userCfgs)) + cfgByModelId := make(map[int64]*entity.UserModelConfig, len(userCfgs)) + for _, uc := range userCfgs { + if uc.ModelConfigId > 0 { + ids = append(ids, uc.ModelConfigId) + cfgByModelId[uc.ModelConfigId] = uc + } + } + models, err := dao.ModelConfig.GetByIds(ctx, ids) + if err != nil || len(models) == 0 { + return &dto.GetUserModelListRes{List: make([]*dto.UserModelItem, 0)} + } + list := make([]*dto.UserModelItem, 0, len(models)) for _, m := range models { sysMax := schemaMaxTokens(m.Schema) item := &dto.UserModelItem{ @@ -208,7 +216,7 @@ func (s *userModelConfigService) GetUserModelList(ctx context.Context, userId in ConcurrencyCount: m.ConcurrencyCount, SystemMaxTokens: sysMax, } - if uc, ok := cfgMap[m.Id]; ok { + if uc, ok := cfgByModelId[m.Id]; ok { item.Configured = true item.UserApiKey = uc.ApiKey item.EndpointUrl = uc.EndpointUrl @@ -223,18 +231,25 @@ func (s *userModelConfigService) GetUserModelList(ctx context.Context, userId in } func (s *userModelConfigService) GetUserModelListPage(ctx context.Context, userId int64, page, pageSize int, modelType, keyword string) *dto.GetUserModelListRes { - models, total, err := dao.ModelConfig.ListPage(ctx, page, pageSize, modelType, keyword) - if err != nil { + // 先查用户配置,获取 model_config_id 列表 + userCfgs, _ := dao.UserModelConfig.GetByUserId(ctx, userId) + if len(userCfgs) == 0 { + return &dto.GetUserModelListRes{List: make([]*dto.UserModelItem, 0)} + } + ids := make([]int64, 0, len(userCfgs)) + cfgByModelId := make(map[int64]*entity.UserModelConfig, len(userCfgs)) + for _, uc := range userCfgs { + if uc.ModelConfigId > 0 { + ids = append(ids, uc.ModelConfigId) + cfgByModelId[uc.ModelConfigId] = uc + } + } + // 根据 model_config_id 查询系统模型 + models, err := dao.ModelConfig.GetByIds(ctx, ids) + if err != nil || len(models) == 0 { return &dto.GetUserModelListRes{List: make([]*dto.UserModelItem, 0)} } list := make([]*dto.UserModelItem, 0, len(models)) - - userCfgs, _ := dao.UserModelConfig.GetByUserId(ctx, userId) - cfgMap := make(map[int64]*entity.UserModelConfig) - for _, uc := range userCfgs { - cfgMap[uc.ModelConfigId] = uc - } - for _, m := range models { sysMax := schemaMaxTokens(m.Schema) item := &dto.UserModelItem{ @@ -244,7 +259,7 @@ func (s *userModelConfigService) GetUserModelListPage(ctx context.Context, userI ConcurrencyCount: m.ConcurrencyCount, SystemMaxTokens: sysMax, } - if uc, ok := cfgMap[m.Id]; ok { + if uc, ok := cfgByModelId[m.Id]; ok { item.Configured = true item.UserApiKey = uc.ApiKey item.EndpointUrl = uc.EndpointUrl @@ -255,5 +270,5 @@ func (s *userModelConfigService) GetUserModelListPage(ctx context.Context, userI } list = append(list, item) } - return &dto.GetUserModelListRes{List: list, Total: total} + return &dto.GetUserModelListRes{List: list, Total: len(list)} }