feat(protocol): /v1 协议去除 fixed 时长模式,统一 options/range
- /v1/models:单值时长也输出 {"mode":"options","options":[N]},不再发 fixed
- 导入/同步校验只接受 options / range,fixed 分支删除,报错「时长模式仅支持 options / range」
- 渠道模型预览的 fixed 展示分支去掉
- 内部存储不变(durations 数组),历史模型与下单逻辑不受影响
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -299,6 +299,13 @@ func normalizeRemoteModel(m upstream.RemoteModel) (ChannelModelSpec, error) {
|
|||||||
default:
|
default:
|
||||||
return spec, fmt.Errorf("未知类型 %q", spec.Kind)
|
return spec, fmt.Errorf("未知类型 %q", spec.Kind)
|
||||||
}
|
}
|
||||||
|
if spec.Duration != nil {
|
||||||
|
switch spec.Duration.Mode {
|
||||||
|
case "options", "range":
|
||||||
|
default:
|
||||||
|
return spec, errors.New("时长模式仅支持 options / range")
|
||||||
|
}
|
||||||
|
}
|
||||||
return spec, nil
|
return spec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,6 +439,9 @@ func (s *ChannelService) ImportModels(ctx context.Context, channelID string, spe
|
|||||||
if upID == "" {
|
if upID == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if spec.Duration != nil && spec.Duration.Mode != "options" && spec.Duration.Mode != "range" {
|
||||||
|
return fmt.Errorf("模型 %s:时长模式仅支持 options / range", upID)
|
||||||
|
}
|
||||||
alias := strings.TrimSpace(spec.Alias)
|
alias := strings.TrimSpace(spec.Alias)
|
||||||
if alias == upID {
|
if alias == upID {
|
||||||
return fmt.Errorf("「%s」别名不能与上游模型同名,留空即可", upID)
|
return fmt.Errorf("「%s」别名不能与上游模型同名,留空即可", upID)
|
||||||
@@ -501,9 +511,6 @@ func modelPatchFromSpec(spec ChannelModelSpec, existing *model.ModelConfig) map[
|
|||||||
}
|
}
|
||||||
if d := spec.Duration; d != nil {
|
if d := spec.Duration; d != nil {
|
||||||
switch d.Mode {
|
switch d.Mode {
|
||||||
case "fixed":
|
|
||||||
patch["durations"] = jsonArray([]string{fmt.Sprintf("%ds", d.Seconds)})
|
|
||||||
patch["duration_min"], patch["duration_max"] = int64(0), int64(0)
|
|
||||||
case "options":
|
case "options":
|
||||||
opts := make([]string, 0, len(d.Options))
|
opts := make([]string, 0, len(d.Options))
|
||||||
for _, s := range d.Options {
|
for _, s := range d.Options {
|
||||||
|
|||||||
@@ -342,8 +342,9 @@ type V1ModelItem struct {
|
|||||||
RefAudioSeconds int64 `json:"refAudioSeconds,omitempty"`
|
RefAudioSeconds int64 `json:"refAudioSeconds,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// V1ModelDur exposes a video model's duration capability: fixed=locked seconds,
|
// V1ModelDur exposes a video model's duration capability: options=discrete
|
||||||
// options=discrete list, range=continuous interval.
|
// list (single-choice models too — "fixed" is never emitted), range=continuous
|
||||||
|
// interval.
|
||||||
type V1ModelDur struct {
|
type V1ModelDur struct {
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
Seconds int64 `json:"seconds,omitempty"`
|
Seconds int64 `json:"seconds,omitempty"`
|
||||||
@@ -433,20 +434,19 @@ func modelDurationSpec(item *model.ModelConfig) *V1ModelDur {
|
|||||||
return &V1ModelDur{Mode: "range", Min: int64(item.DurationMin), Max: int64(item.DurationMax)}
|
return &V1ModelDur{Mode: "range", Min: int64(item.DurationMin), Max: int64(item.DurationMax)}
|
||||||
}
|
}
|
||||||
opts := repo.JSONStrings(item.Durations)
|
opts := repo.JSONStrings(item.Durations)
|
||||||
switch len(opts) {
|
if len(opts) == 0 {
|
||||||
case 0:
|
|
||||||
return nil
|
return nil
|
||||||
case 1:
|
|
||||||
return &V1ModelDur{Mode: "fixed", Seconds: int64(parseDurationSeconds(opts[0]))}
|
|
||||||
default:
|
|
||||||
vals := make([]int64, 0, len(opts))
|
|
||||||
for _, d := range opts {
|
|
||||||
if n := parseDurationSeconds(d); n > 0 {
|
|
||||||
vals = append(vals, int64(n))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &V1ModelDur{Mode: "options", Options: vals}
|
|
||||||
}
|
}
|
||||||
|
vals := make([]int64, 0, len(opts))
|
||||||
|
for _, d := range opts {
|
||||||
|
if n := parseDurationSeconds(d); n > 0 {
|
||||||
|
vals = append(vals, int64(n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(vals) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &V1ModelDur{Mode: "options", Options: vals}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserBalance — GET /v1/balance. Re-reads the user row so the number is live.
|
// UserBalance — GET /v1/balance. Re-reads the user row so the number is live.
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ type RemoteModel struct {
|
|||||||
|
|
||||||
// RemoteDuration mirrors the duration spec of a vivid model item.
|
// RemoteDuration mirrors the duration spec of a vivid model item.
|
||||||
type RemoteDuration struct {
|
type RemoteDuration struct {
|
||||||
Mode string `json:"mode"` // fixed | options | range
|
Mode string `json:"mode"` // options | range
|
||||||
Seconds int64 `json:"seconds,omitempty"`
|
Seconds int64 `json:"seconds,omitempty"`
|
||||||
Min int64 `json:"min,omitempty"`
|
Min int64 `json:"min,omitempty"`
|
||||||
Max int64 `json:"max,omitempty"`
|
Max int64 `json:"max,omitempty"`
|
||||||
|
|||||||
@@ -126,8 +126,7 @@ function specText(s) {
|
|||||||
const parts = []
|
const parts = []
|
||||||
if (s.qualities?.length) parts.push(s.qualities.join('/'))
|
if (s.qualities?.length) parts.push(s.qualities.join('/'))
|
||||||
if (s.duration) {
|
if (s.duration) {
|
||||||
if (s.duration.mode === 'fixed') parts.push(`固定 ${s.duration.seconds}s`)
|
if (s.duration.mode === 'range') parts.push(`${s.duration.min}-${s.duration.max}s`)
|
||||||
else if (s.duration.mode === 'range') parts.push(`${s.duration.min}-${s.duration.max}s`)
|
|
||||||
else if (s.duration.mode === 'options') parts.push((s.duration.options || []).map((d) => `${d}s`).join('/'))
|
else if (s.duration.mode === 'options') parts.push((s.duration.options || []).map((d) => `${d}s`).join('/'))
|
||||||
}
|
}
|
||||||
const refs = []
|
const refs = []
|
||||||
|
|||||||
Reference in New Issue
Block a user