원클릭으로
slide-story-streaming
// 为 `markdown-flow-ui` 的幻灯片(`Slide`)新增或更新可模拟流式播放的 `story` 时使用本技能,尤其适用于通过 `StreamingSlidePreview` 渲染、且为单元素 iframe 承载 `html` 的幻灯片场景。
// 为 `markdown-flow-ui` 的幻灯片(`Slide`)新增或更新可模拟流式播放的 `story` 时使用本技能,尤其适用于通过 `StreamingSlidePreview` 渲染、且为单元素 iframe 承载 `html` 的幻灯片场景。
保持 `markdown-flow-ui` 中基于 iframe 的幻灯片(`Slide`)步骤常驻挂载,通过 CSS `display` 切换可见性。当幻灯片导航需要降低 iframe 重载耗时、要把 `is_renderable` 驱动的卸载渲染改为“隐藏但已挂载”策略、或在预加载改造中保持 `is_new`、`diff`、交互步骤语义不变时使用本技能。
修复或排查 `markdown-flow-ui` 的 `ContentRender` 在关闭打字机效果时首屏空白、helper 行先于正文出现等首次渲染时序问题时使用本技能。
为 `markdown-flow-ui` 的 `ContentRender` 新增或更新“后端整句到达、前端按固定节奏做打字机输出”的 story 时使用本技能。
修改 `markdown-flow-ui` 的 `MarkdownFlowEditor` 国际化语言时使用本技能,尤其适用于新增 locale、补充编辑器文案资源、以及让上游项目传入的语言配置安全落到编辑器内部的场景。
统一 `markdown-flow-ui` 中 `ContentRender`、`IframeSandbox`、`Slide` 等场景的 loading 遮罩表现时使用本技能。
当调整 `markdown-flow-ui` 的 `Slide` 移动端播放器控制栏,尤其涉及 `playerCustomActions` 的桥接型节点、占位空槽和 more icon 入口时使用本技能。
| name | slide-story-streaming |
| description | 为 `markdown-flow-ui` 的幻灯片(`Slide`)新增或更新可模拟流式播放的 `story` 时使用本技能,尤其适用于通过 `StreamingSlidePreview` 渲染、且为单元素 iframe 承载 `html` 的幻灯片场景。 |
当幻灯片 story 需要流式行为时,保持数据最小化并复用现有 StreamingSlidePreview,不要重复实现定时器逻辑。
markup 放到独立常量,并与其他 story 示例数据邻近维护。html 元素。isNew: true,让步骤表现与新到达幻灯片一致。StreamingSlidePreview 渲染 story,保留现有 SSE 风格逐字符流式行为。story 的流式意图。story 需要 history + submit-to-stream,先预载历史 fixture,聚焦最新交互标记,再在 story 的 onSend 回调中回放过滤后的 run-stream fixture。Slide story 中通过 playerCustomActions 外部传入自定义按钮节点,并使用独立 preview wrapper 承载按钮状态与点击行为。示例 story 名称应清晰,并保持英文命名。
每个 story 聚焦单一场景,不要混杂多种幻灯片行为。
复用 createExampleElement 等现有辅助工厂,确保元素契约一致。
如果内容目标是 iframe sandbox,使用 type: "html",不要新增无必要元素类型。
当流式更新复用同一 audio_url 时,保留当前播放进度,仅在 URL 实际变化时重置播放。
当流式负载持续增加 sequence_number 时,不要用它作为当前音频项的重置标识;优先使用稳定元素级 key,例如 element_bid。
当 Slide.stories.tsx 需要把 测试数据.json 这类 run-stream 事件转成 elementList 并对齐 ai-shifu 听课模式时,应采用 itemType + element_bid 的稳定序号映射(含冲突顺延、失活键清理、页码重算),不要直接信任流式包里的递增 sequence_number。
当 history + submit-to-stream story 需要从 run-stream fixture 中截取“交互提交后的 follow-up 流”时,优先按当前 interaction 的变量名命中对应 variable_update,并从其后的首个 element 事件开始回放;不要写死依赖某段文案内容匹配,否则 fixture 文案一变 story 会静默不触发。
当 markdown-flow-ui 需要复现 ai-shifu 听课模式的 SSE 问题时,run-stream 的 normalize / audio source resolve / audio segment merge / upsert elementList 必须复用同一套 listen-mode helper,保持 audioTracks 优先、重复分段可提升 is_final、视频 iframe 优先识别为 video,不要在 story 内再维护一份裁剪版逻辑。
当 Slide 播放器在流式追加 audio_segments 或新增后续元素时出现“当前音频被中断并重播”,应把播放状态从 index 驱动改为稳定 audioKey 驱动,并避免把 index 拼进播放器重置 key。
当同一 element_bid 在流式过程中会依次经历“无音频 -> 部分 audio_segments -> 更多 audio_segments -> 完整 audio_url/subtitle_cues”这类增量 upsert 时,不要把这些同元素音频字段补齐过程误判成新的播放上下文并重置到 0ms;否则就会出现同一句反复重播、字幕卡在首条 cue 不推进的假循环。
当 run-stream 在 audio_complete 后又紧接一次 block finalize,可能连续产出两个 audio_url 已完整、但 element_bid 相同的 final snapshot;消费层必须按 element_bid 覆盖同一元素,并忽略仅有 sequence_number/run_event_seq 递增带来的“伪新增”。
当同一元素同时带有 audio_segments 与 audio_url 时,应在渲染链路中约束为单一播放源(优先 segment 源),避免双源竞争触发重复播放或中途重置。
当流式 elementList 的内容元素在首段阶段仅出现 text(尚未出现非 text 内容)时,应在首个 text 之前插入 empty-ppt 占位页,待后续出现非 text 内容后再按正常内容序列渲染。
当同一份流式 fixture 既需要保留原始 baseline 场景,又需要复现 empty-ppt 占位场景时,应拆成两个独立 story,并通过 preview wrapper 的显式开关控制是否插入占位页,避免污染原 story。
若需要在 markdownflow-slide--full-viewport-single-slide 复现该行为,优先在对应 preview wrapper(如 RunStreamSlidePreview)里做 elementList 预处理,再传给 Slide,避免修改通用组件行为。
当需要复现“首次 SSE 出交互块,用户提交后第二次 SSE 触发音频卡住/循环”的问题时,应新增双阶段 story:首段回放 测试数据2.json,等待 onSend 后再回放 测试数据.json,并保持两段都走同一套 listen-mode 对齐的 elementList 组装逻辑。
当需要复现“run SSE 停在某个 speakable element、既没有 done 也没有 close、导致听课模式一直 buffering”的问题时,应直接基于 测试数据.json 截断到“同一 element_bid 最后一个仍无可播音频的 speakable 快照”,不要手写伪造 payload;这样能更稳定地对齐 ai-shifu 的真实卡住位置。
当这类“卡住的 SSE” story 还需要在 Storybook 中体现超时兜底时,优先在 run-stream preview wrapper 上增加可选 streamTimeoutMs,并在超时后由 story 自己维护错误态;Slide 视图不要追加 error marker element 到画面内部,而应在 story 外层显示 alert,并通过独立开关关闭 loading。
当同一个 Slide SSE story 需要像 ai-shifu 一样在 Storybook 里切换“听课视图 / 阅读视图”时,优先在 story preview wrapper 顶部增加单独一行的分段切换按钮;Slide 继续复用原有 run-stream 回放,ContentRender 视图只需遍历当前 elementList 并按顺序渲染字符串 content,不要跟随 Slide 的 is_renderable 门禁去裁剪文本元素,也不要顺手加入额外业务能力。
当交互块提交后、同一步里马上开始收到 follow-up 可播语音时,不要继续强制打开 resolved interaction overlay;否则交互步会和后续语音抢同一个 step,触发重复 reset,表现为首段音频反复从头播放。
当播放器自动播放或用户通过 prev/next 重新进入 interaction step 时,应重新打开 interaction overlay 并点亮 notes icon;但如果只是当前 interaction step 在原地继续流式追加入 follow-up 语音,则保持 overlay 关闭,避免和后续语音竞争同一步播放状态。
当播放器正停在“最后一个、且已提交完成的 interaction step”上,而 SSE 首次 append 进来的是新的 marker(例如 html 首屏)时,应立即把当前索引切到这个新 marker,不要继续停在旧 interaction step 上再额外等待 interaction auto-close 或 markerAutoAdvanceDelay,否则会出现“新页面到了但几秒后才翻页”的体感延迟。
当纯流式播放里“当前 step 已经结束,但当时还没有下一步”,之后 SSE 再 append 出新的 marker 时,应在 Slide 播放层基于“marker 数量新增 + canGoNext 从 false 变 true + 当前 step 已完成”立即补一次 goNext();不要只在 useSlide 里改索引,否则容易误判同一步 append 和真正的新 step 解锁。
当 Slide 的静默 marker-only 步骤需要调整自动前进节奏时,应优先暴露组件级延时参数(如 markerAutoAdvanceDelay),并让默认值覆盖常见 html-to-html 切页场景,避免把间隔硬编码在 story 或业务侧定时器里。
当 run-stream 事件把 audio_segments / audio_url 附带在非 is_speakable 的非 text 元素(如 html)上时,应在 upsert 后将音频归属迁移到其前一个最近的 is_speakable text 元素,并清空该非 text 元素音频字段,避免语音状态错位。
当需要验证 elementList.audio_segments[].audio_data 直播能力时,优先从现有 run-stream fixture 解析并提取首个可播的 is_speakable text 元素,组装成单步 marker story,避免在 story 里手工维护超长 base64 常量。
当目标只是快速听 audio_data 的真实发音,不必走 Slide 播放链路;应基于传入 elementList 过滤可播放音频段并 map 渲染原生 <audio controls> 列表,便于逐条试听和比对。
当需要排查 text 元素的字幕与音频不同步问题时,应新增独立调试 story:直接传入单个 type=text element,支持切换 audio_url 与 audio_segments 播放源,并同步展示毫秒级音频进度、当前命中的 subtitle 文本、以及所有 subtitle_cues 的 start_ms/end_ms 高亮表格。
播放器的“正在播放”UI 状态必须以后续 audio 元素真实触发 play/canplay 为准;在等待流式音频片段、等待补齐 audio_url 或等待缓冲期间,只显示 buffering,不要提前把按钮切成暂停态。
当单个 html 元素在 iframe sandbox 内以 append-only 方式持续流式增长时,不要每个 chunk 都立即重刷 iframe;应对增量更新做轻量 debounce,并在 iframe 内部 DOM 替换时短暂保留上一帧内容覆盖层,以减少 replaceChildren 带来的闪烁感。
若 slide story 的 iframe sandbox 在流式阶段主要表现为图片或圆角头像持续闪动,应优先排查运行时 Tailwind 注入;相比继续调 React key,更可能是 iframe 内 Tailwind JIT 因 class 持续变化而反复重建样式表,此时应优先复用宿主页面静态样式或注入预构建 CSS,避免在流式过程中依赖运行时 Tailwind 编译。
当 sandbox iframe 的内容主要依赖 Tailwind utility class 且会持续流式更新时,优先注入预构建好的静态 Tailwind CSS(例如库产物中的 markdown-flow-ui-lib.css),不要在该场景里继续使用运行时 Tailwind script;运行时编译更容易引发图片区域闪动、样式抖动或局部重绘。
若当前 sandbox 仍必须继续使用 Tailwind 浏览器脚本,则图片闪动问题应进一步收敛到 img 首次可渲染时机与 iframe head 样式稳定窗口:流式更新包含图片时,先保留上一帧覆盖层,再等待新图片可解码且 head 中样式不再继续变动后再揭开,避免头像在 Tailwind 运行时样式重建期间暴露出来。
当播放器右侧分组新增外部自定义按钮时,应同步处理 notes 相关浮层箭头偏移与移动端按钮栅格列数,避免新增按钮后交互定位或布局错位。
当 Slide.playerCustomActions 需要拿到当前播放 step 的 element 时,应优先支持 render function 形式 (context) => ReactNode,并通过 context.currentElement/currentIndex 在点击时向外透传,不要在 story 或业务层自行猜测当前播放器状态。
当 Slide.playerCustomActions 需要在按钮高亮期间暂停当前播放进度时,应由 Slide 暴露 context.isActive / setActive / toggleActive 并配合默认开启的 playerCustomActionPauseOnActive 统一接管暂停与恢复,不要在业务侧重复维护音频和自动播放的暂停逻辑。
当 playerCustomActionPauseOnActive 为 true 时,只要 Slide 发生 step 切换(无论是用户前进后退还是自动播放推进),都应立即重置自定义按钮高亮态,避免新 step 继承上一 step 的暂停状态。
当 iframe sandbox 使用 DaisyUI 并出现“系统深色模式触发内容变暗”时,优先在 iframe 引导阶段锁定 documentElement 的 data-theme="light" 与 color-scheme: light,不要依赖宿主页面主题,以避免 @media (prefers-color-scheme: dark) 覆盖 bg-base-* 等语义色变量。
当 Slide 在同一步里持续收到 SSE append 更新时,不要因为 elementList 长度变化就重置当前播放序列;只有 step / interaction 播放上下文真正变化时才允许 reset。若当前 step 音频已经自然结束,也不要因后续 append 把同一 audioKey 从头再播一次。
当 Slide 处理流式音频 loading 时,is_speakable 步骤应在“尚未返回任何可播音频”时显示 loading;只要首个 audio_segments 或 audio_url 到达就立即关闭 loading 开始播放;若当前音频播完但下一段还未返回,可再次显示 loading;一旦该步骤音频被判定彻底结束或用户手动切换 marker,必须立即关闭 loading。