Files
happy-life-star/docs/superpowers/specs/2026-07-19-scriptview-unified-chat-design.md
T
peanut b2df7d4de0 docs:ScriptView 统一对话页改造设计
将澄清/大纲/小说生成从独立视图改为统一对话流消息。
viewState 简化为 home+chat,扩展 resultMessages 消息模型
支持 kind: text/card/outline/novel,novel_done 后切换旧接口
做后续对话修改。
2026-07-19 14:45:45 +08:00

12 KiB
Raw Blame History

author, created_at, purpose
author created_at purpose
AI Assistant 2026-07-19 ScriptView 心愿实现页面统一对话流改造设计,将澄清/大纲/小说生成全部纳入单一对话页面

ScriptView 统一对话页改造设计

概述

将小程序心愿实现页面(ScriptView.vue)的生成流程从"多独立视图切换"改为"单一对话页面流"。用户从提交心愿开始,始终停留在同一个对话页面,澄清问答、大纲确认/修改、小说流式生成、后续对话修改都作为对话消息依次出现在聊天流里。

背景与问题

当前架构(AS-IS

ScriptView.vue 用 viewState 在 5 个独立视图间切换:

viewState 内容 对话页?
home 心愿输入首页(灵感推荐+输入框)
generating 生成中加载页(用户气泡+loading orbit
clarifying 澄清卡片(只有 ClarificationCard
outlining 大纲确认(大纲+确认/修改按钮)
result 结果对话页(MessageCard+用户气泡+输入栏)

问题:澄清、大纲、小说生成阶段用户"不在对话页面",体验割裂。用户要求"心愿实现时必须一直处于对话的页面,必须支持对话和选择的澄清判断"。

可复用资产

result 视图已有完整对话机制:

  • resultMessages 数组 + addResultMessage({id, role, kind, content, pending, time})
  • MessageCard 组件渲染 AI 消息
  • 用户气泡渲染用户消息
  • 底部输入栏 sendChat(当前接旧接口 streamScriptChat
  • 滚动控制(keepResultAtBottomresultScrollTop 等)

目标架构(TO-BE

viewState 简化

从 5 个视图简化为 2 个

home(首页:灵感推荐+输入框,保留不变)
  ↓ 用户提交心愿
chat(统一对话页:澄清→大纲→小说→后续修改 全在这里)

删除独立的 generating/clarifying/outlining/novel-generating 视图。

统一对话消息模型

复用 resultMessages + addResultMessage,扩展 kind 字段:

{
  id,           // createMessageId()
  role,         // 'user' | 'assistant'
  kind,         // 'text' | 'card' | 'outline' | 'novel'
  content,      // 文本内容(text/novel
  pending,      // 是否加载中(novel 流式时 true)
  time,         // formatMessageTime()
  card?,        // kind:'card' 时携带澄清卡片数据
  outline?,     // kind:'outline' 时携带大纲数据
  submitted?,   // kind:'card' 时标记是否已提交(禁用交互)
  confirmed?,   // kind:'outline' 时标记是否已确认/修改(禁用交互)
  failed?       // 是否失败(error 事件)
}

消息类型与渲染

chat 视图对话区用 v-for 遍历 resultMessages,根据 kind 渲染不同气泡内容:

kind role 渲染方式
text user 用户气泡(chat-bubble user
text assistant AI 文本气泡(chat-bubble system),含状态提示/错误
card assistant AI 气泡内嵌 ClarificationCardsubmitted 后禁用交互并显示已选答案
outline assistant AI 气泡内嵌大纲卡片+确认/修改按钮,confirmed 后禁用按钮
novel assistant AI 气泡内嵌小说正文,pending 时流式逐字追加,pending=false 后完成

新消息追加到底部,keepResultAtBottom() 自动滚动跟随。

SSE 事件 → 对话消息映射

handleShortNovelEvent 改为往统一对话流追加/更新消息:

SSE 事件 对话流行为
status 更新底部 loading 提示文案(不追加独立消息)
clarification_card addResultMessage({role:'assistant', kind:'card', card: payload.card, submitted:false})
outline_created addResultMessage({role:'assistant', kind:'outline', outline: payload.outline, confirmed:false})
novel_start addResultMessage({role:'assistant', kind:'novel', content:'', pending:true})
novel_delta 往最后一条 kind:'novel' 消息的 content 追加 payload.delta
novel_done 标记最后一条 novel 消息 pending=false,保存 scriptId/conversationId/currentVersionMessageId
error DAILY_GENERATION_IN_PROGRESS 且有 session_id:保存 resumeableSessionId,更新底部 loading 为失败态+继续创作入口;其他错误:追加 kind:'text', role:'assistant', failed:true 消息

用户交互 → 接口调用

每张卡片/大纲消息内部渲染交互按钮,用户操作时先追加一条 user 消息到对话流,再调接口

阶段 交互 追加 user 消息 接口调用
生成 ClarificationCard 提交 {kind:'text', content: 答案} followupStream(action:'answer_clarification', payload:{answer})
生成 大纲"确认" {kind:'text', content:'确认大纲'} followupStream(action:'confirm_outline')
生成 大纲"修改" {kind:'text', content: 修改意见} followupStream(action:'modify_outline', payload:{feedback})
生成 失败"继续创作" {kind:'text', content:'继续之前的创作'} followupStream(action:'retry')
后续 novel_done 后底部输入 {kind:'text', content: 修改建议} streamScriptChat(旧接口,支持改写/继续/版本)

接口切换边界

  • 生成流程(澄清→大纲→小说):用新接口 startNovelStream / followupStream
  • novel_done 后:保存记录后,底部输入栏 sendChat 切换用现有旧接口 streamScriptChat + createMessage(已成熟,支持改写/继续/版本切换)

切换时机:novel_done 事件处理时,设置 generationPhase = 'done',底部输入栏根据该标志切换接口。两套接口的消息都进同一个 resultMessages 对话流,用户无感知。

交互态管理

卡片消息(kind:'card'

  • 渲染 ClarificationCard,传入 :card="message.card"
  • submitted=false 时:正常交互,@submit 触发 submitClarification
  • submitClarification 内:设置 message.submitted=true(禁用卡片),追加 user 消息,调 followupStream
  • submitted=true 时:卡片显示"已回答:xxx",禁用交互

大纲消息(kind:'outline'

  • 渲染大纲内容(title/logline/beats/ending+ 确认/修改按钮
  • confirmed=false 时:显示输入框+确认+修改按钮
  • 用户确认/修改后:设置 message.confirmed=true(隐藏按钮),追加 user 消息,调 followupStream
  • confirmed=true 时:只展示大纲内容,不再有按钮

小说消息(kind:'novel'

  • pending=true 时:流式追加 novel_delta 文本,显示打字光标
  • pending=false(novel_done)时:完整小说正文,无光标,可复制/朗读

chat 视图模板结构

<view v-else-if="viewState === 'chat'" class="chat-page">
  <!-- 顶部历史 + 关闭 -->
  <view class="chat-top-actions">...</view>

  <!-- 对话流 -->
  <scroll-view class="chat-scroll" scroll-y ...>
    <view class="chat-scroll-content">
      <view v-for="msg in resultMessages" :key="msg.id">
        <!-- user text -->
        <view v-if="msg.role==='user' && msg.kind==='text'" class="chat-bubble user">
          <text>{{ msg.content }}</text>
          <text class="bubble-time">{{ msg.time }}</text>
        </view>

        <!-- assistant card -->
        <view v-else-if="msg.kind==='card'" class="chat-bubble system">
          <ClarificationCard
            v-if="!msg.submitted"
            :card="msg.card"
            @submit="answerClarification(msg, $event)"
          />
          <view v-else class="card-answered">
            <text>已回答{{ msg.answer }}</text>
          </view>
        </view>

        <!-- assistant outline -->
        <view v-else-if="msg.kind==='outline'" class="chat-bubble system">
          <!-- 大纲内联渲染title/logline/beats/ending),复用当前 outlining 视图样式 -->
          <view v-if="msg.outline?.title" class="outline-title">{{ msg.outline.title }}</view>
          <view v-if="msg.outline?.logline" class="outline-logline">{{ msg.outline.logline }}</view>
          <view v-if="Array.isArray(msg.outline?.beats)" class="outline-beats">
            <view v-for="(beat, idx) in msg.outline.beats" :key="idx" class="beat-item">
              <text class="beat-order">{{ beat.order || idx + 1 }}</text>
              <view class="beat-content">
                <text class="beat-title">{{ beat.title }}</text>
                <text v-if="beat.summary" class="beat-summary">{{ beat.summary }}</text>
              </view>
            </view>
          </view>
          <view v-if="msg.outline?.ending" class="outline-ending">
            <text class="ending-label">结局</text>
            <text class="ending-text">{{ msg.outline.ending }}</text>
          </view>
          <view v-if="!msg.confirmed" class="outline-actions">
            <input v-model="msg.feedback" placeholder="如需修改请输入意见" />
            <button @click="modifyOutline(msg)">修改大纲</button>
            <button @click="confirmOutline(msg)">确认大纲</button>
          </view>
        </view>

        <!-- assistant novel -->
        <view v-else-if="msg.kind==='novel'" class="chat-bubble system">
          <text>{{ msg.content }}<text v-if="msg.pending" class="typing-cursor">|</text></text>
        </view>

        <!-- assistant text (status/error) -->
        <view v-else class="chat-bubble system">
          <text :class="{ failed: msg.failed }">{{ msg.content }}</text>
        </view>
      </view>

      <!-- 底部 loading -->
      <view v-if="generating" class="chat-loading">
        <view class="loading-orbit streaming">...</view>
        <text>正在为你重写人生</text>
      </view>
      <!-- 失败继续创作入口 -->
      <view v-if="generationStatus==='failed' && resumeableSessionId" class="resume-action">
        <button @click="resumeSession">继续创作</button>
      </view>

      <view :id="resultScrollAnchor" class="chat-scroll-anchor"></view>
    </view>
  </scroll-view>

  <!-- 底部输入栏novel_done 后启用 -->
  <view class="chat-input-bar">
    <textarea v-model="chatInput" @confirm="sendChat" />
    <view @click="sendChat">发送</view>
  </view>
</view>

文件改动清单

文件 操作 说明
mini-program/src/pages/main/ScriptView.vue 修改 删除 clarifying/outlining/novel-generating 独立视图;新增统一 chat 视图;扩展 addResultMessage 支持 kind/card/outline/submitted/confirmed;改造 handleShortNovelEvent 往对话流追加消息;submitClarification/confirmOutline/modifyOutline 改为操作对话消息;sendChat 按 generationPhase 切换接口
mini-program/src/components/ClarificationCard.vue 不变 复用,由父组件控制 submitted 态

不在本次范围内

  • 首页 home 的灵感推荐/语音输入 — 保留不变
  • MessageCard 的版本切换/改写/继续功能 — 保留不变(novel_done 后用旧接口时复用)
  • 后端接口 — 不变(新接口 followupStream + 旧接口 streamScriptChat 都已就绪)
  • 历史剧本库 — 保留不变

验收标准

  • 用户提交心愿后进入对话页,用户消息作为气泡显示
  • 澄清卡片作为 AI 消息出现在对话流,用户回答后卡片变为"已回答"态
  • 大纲作为 AI 消息出现,确认/修改后按钮消失
  • 小说流式生成作为一条 AI 消息逐字出现
  • novel_done 后底部输入栏启用,继续修改用旧接口正常工作
  • 失败时(DAILY_GENERATION_IN_PROGRESS)对话流内显示继续创作入口
  • 整个过程不切换视图,用户始终在同一个对话页面