Files
TrueGrowth/openspec/changes/add-suno-lyrics-task-and-canvas-flow/design.md

6.3 KiB
Raw Blame History

Context

仓库当前已经为 Suno 音乐生成铺好了主路径:

  • runtime-model-discovery 会把 lyricsmusicsuno 等标识统归到 audio
  • provider-routing 会为 Suno 推断 tuzi.suno.music 绑定
  • audio-api-service 负责提交 /suno/submit/music、轮询 /suno/fetch/{taskId} 并提取音频 URL
  • 任务队列、自动插入和任务面板会把 TaskType.AUDIO 默认当成“可播放音频资产”

这条链路在音乐生成场景下是成立的,但在歌词场景下不成立:

  • 歌词任务仍然属于 Suno 的异步任务
  • 但 fetch 结果里的核心字段是:
    • data.data.text
    • data.data.title
    • data.data.tags
  • 没有 audio_url

因此,当前代码里“路由类型 = 结果类型 = 画布落地方式”的隐含绑定需要被拆开。

Goals / Non-Goals

  • Goals:
    • 在不新增独立模态的前提下支持 Suno lyrics 动作
    • 保持 audio 路由、默认预设和主入口不变
    • 显式区分 Suno 的提交动作和最终结果类型
    • 让歌词任务进入现有任务队列、恢复、重试和画布插入链路
    • 最大化复用现有文本插入能力,而不是新造歌词节点系统
  • Non-Goals:
    • 不把歌词能力改造成独立 lyrics 模态
    • 不在本次中实现歌词编辑器、版本对比或歌词协作能力
    • 不覆盖所有 Suno 高级动作
    • 不重构整个任务系统为严格的 discriminated union 体系

Decisions

  • Decision: 歌词能力继续归属 audio 路由族

    • 预设配置仍然只维护 audio 路由
    • AI 输入栏也仍然通过 音频 模式进入
    • 通过 sunoAction = 'music' | 'lyrics' 区分当前执行动作
  • Decision: 显式拆分“提交动作”和“结果类型”

    • musiclyrics 决定 submit path、请求字段和结果提取器
    • resultKind 决定任务队列展示和画布落地方式
    • TaskType 首批保持为 AUDIO,避免把歌词任务拆成新的队列大类
  • Decision: lyrics 走独立 submit但继续共用 fetch

    • music -> POST /suno/submit/music
    • lyrics -> POST /suno/submit/lyrics
    • 两者都通过 GET /suno/fetch/{task_id} 获取状态与结果
  • Decision: 任务结果模型以增量字段扩展为主

    • 保持现有图片、视频、音频路径影响最小
    • 为任务结果新增:
      • resultKind
      • lyricsText
      • lyricsTitle
      • lyricsTags
    • url 对歌词结果不再视为必填
  • Decision: 歌词落画布复用现有文本插入链

    • 任务面板点击“插入”时,把歌词结果格式化成 markdown / text 内容
    • 自动插入同样走文本插入能力
    • 不创建音频节点,也不伪造音频卡片
  • Decision: UI 参数按动作分域

    • music 保留现有参数:
      • mv
      • title
      • tags
      • continueSource
      • continueClipId
      • continueAt
    • lyrics 只需要:
      • prompt
      • 可选的内部 notifyHook
    • 音乐专属参数在歌词动作下不显示、不提交

Proposed Data Shape

type SunoAction = 'music' | 'lyrics';

type SunoResultKind = 'audio' | 'lyrics';

interface SunoGenerationRequest {
  prompt: string;
  model?: string;
  modelRef?: ModelRef | null;
  sunoAction?: SunoAction;
  notifyHook?: string;
  title?: string;
  tags?: string;
  mv?: string;
  continueClipId?: string;
  continueAt?: number;
  params?: Record<string, unknown>;
}

interface TaskResult {
  resultKind?: 'image' | 'video' | 'audio' | 'lyrics' | 'chat';
  url?: string;
  urls?: string[];
  format: string;
  size: number;
  title?: string;
  previewImageUrl?: string;
  providerTaskId?: string;
  primaryClipId?: string;
  clipIds?: string[];
  clips?: AudioClipResult[];
  lyricsText?: string;
  lyricsTitle?: string;
  lyricsTags?: string[];
}

Provider Binding Direction

首批不建议为 lyrics 新增独立模态 binding而是在现有 audio binding metadata 上增加动作表:

interface ProviderAudioBindingMetadata {
  defaultAction?: 'music';
  supportedActions?: Array<'music' | 'lyrics' | string>;
  actions?: Record<
    string,
    {
      submitPath: string;
      resultKind: 'audio' | 'lyrics';
      requestFields: string[];
    }
  >;
  versionField?: string;
  versionOptions?: string[];
  defaultVersion?: string;
  supportsContinuation?: boolean;
  supportsUploadContinuation?: boolean;
  supportsTags?: boolean;
  supportsTitle?: boolean;
}

对 Suno 的首批映射可收敛为:

{
  operation: 'audio',
  protocol: 'tuzi.suno.music',
  pollPathTemplate: '/suno/fetch/{taskId}',
  metadata: {
    audio: {
      defaultAction: 'music',
      supportedActions: ['music', 'lyrics'],
      actions: {
        music: {
          submitPath: '/suno/submit/music',
          resultKind: 'audio',
          requestFields: ['prompt', 'mv', 'title', 'tags', 'continue_clip_id', 'continue_at']
        },
        lyrics: {
          submitPath: '/suno/submit/lyrics',
          resultKind: 'lyrics',
          requestFields: ['prompt', 'notify_hook']
        }
      }
    }
  }
}

Canvas Formatting Strategy

歌词插入画布时优先生成语义化文本,而不是直接丢一大段纯文本:

# 战斗进行时

标签: EDM, 激烈的

[Chorus]
...

这样可以直接复用现有:

  • markdown 解析为卡片
  • 纯文本回退为文本元素

两条路径。

Risks / Trade-offs

  • 风险: TaskType.AUDIO 继续承载歌词任务会让部分旧代码仍然误判为音频资产

    • Mitigation: 统一改为优先判断 result.resultKind
  • 风险: 把 url 改为非必填会触发较多类型修正

    • Mitigation: 先用增量字段方式扩展,聚焦实际消费点改造
  • 风险: 任务队列过滤仍归入“音频”类,用户可能会觉得歌词不是音频

    • Mitigation: 在 item 级别增加明确的动作标签,如“歌词”
  • 风险: 画布文本插入格式不稳定,影响可读性

    • Mitigation: 在服务层统一生成 markdown 模板,不把格式拼装分散到多个组件里

Implementation Outline

  1. 扩展 Suno 请求和 binding metadata加入 lyrics 动作
  2. 抽出共享 fetch 轮询与 action-specific 结果提取器
  3. 扩展任务结果模型与 IndexedDB 读写结构
  4. 让 AI 输入栏按动作切换参数面板
  5. 让任务队列、恢复与重试按 resultKind 分支
  6. 让手动插入与自动插入把歌词结果落到文本插入链路