@tfdesign/b-end 1.0.13 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tfdesign/b-end",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "TFDS B-end React components + Tailwind v4 theme.css; self-contained npm install (no monorepo clone required).",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "system": "b-end",
3
3
  "skill": "tfds",
4
- "generatedAt": "2026-05-12T13:53:18.654Z",
4
+ "generatedAt": "2026-05-13T05:01:47.099Z",
5
5
  "summary": "B 端 COMPONENTS 37 条 + 列表页模板 4 + PATTERNS 6;import 一律来自 @tfdesign/b-end。",
6
6
  "components": [
7
7
  {
@@ -848,7 +848,7 @@
848
848
  "kind": "component",
849
849
  "name": "Card",
850
850
  "category": "business",
851
- "description": "业务信息摘要卡片,支持数据卡片、商品卡片、信息卡片1和信息卡片2。颜色使用遵循背景反衬原则:灰色背景用白底卡,白色背景用灰底卡。",
851
+ "description": "业务信息摘要卡片,支持数据卡片、商品卡片、信息卡片1和信息卡片2。所有卡片分类统一遵循背景反衬原则:灰色/浅灰/非纯白背景用白底卡,纯白背景用灰底卡。",
852
852
  "import": {
853
853
  "from": "@tfdesign/b-end",
854
854
  "default": "Card"
@@ -1096,9 +1096,9 @@
1096
1096
  "【信息卡片2视觉】info2 不包含柔光、流动光带或特殊 hover 位移;背景颜色、边框颜色和 hover 投影完全复用普通 Card 的 color=white / grey 规则。animatedTone 只影响左上图标容器色系,不影响卡片背景、边框和底部标签。",
1097
1097
  "【信息卡片2图标 Hover】卡片 hover 时,左上图标容器必须自动切换为当前 animatedTone 对应的 hover 背景、纯白 icon、透明描边:brand→brand-500、blue→blue-500、purple→purple-500、green→green-500、orange→orange-500、grey→black。禁止 hover 后继续显示浅底、深色 icon 或描边色。",
1098
1098
  "【颜色】color=white 使用 65% 白底 + 白色描边;color=grey 使用 Blue Grey 100 背景 + Blue Grey 300 描边",
1099
- "【容器映射(强约束)】Card 的颜色必须与所处背景做反衬:当 Card 所在父容器是“灰底页面/灰底区块”(如 blueGrey-200 页面底、白卡内的浅灰分区)→ Card 用 `color=\"white\"`;当父容器本身是“白底卡片/白底面板”→ Card 用 `color=\"grey\"`,用灰底卡片做二级区块分层,避免白底叠白底",
1099
+ "【容器映射(强约束)】Card 的颜色必须与所处背景做反衬,而且这条规则对所有 Card 分类统一生效:当 Card 所在父容器是“灰底页面/灰底区块/浅灰分区/其他非纯白背景”→ Card 用 `color=\"white\"`;当父容器本身是“纯白底卡片/纯白底面板”→ Card 用 `color=\"grey\"`,用灰底卡片做二级区块分层,避免白底叠白底",
1100
1100
  "【配置面板颜色】所有 Card 分类(数据卡片、商品卡片、信息卡片1、信息卡片2)都必须优先展示“颜色”配置项,并提供白底 / 灰底两种选择。平台预览中切换右上角画布底色时,卡片颜色必须按反衬规则自动同步:灰底画布 → 白底卡片,白底画布 → 灰底卡片。",
1101
- "【一句话记忆】如果卡片背景环境是灰色,就选“白底”分类卡片;如果卡片背景环境是纯白色,就选“灰底”分类卡片。不要让 Card 和父级背景同色相贴。",
1101
+ "【一句话记忆】如果卡片背景环境是灰色、浅灰色或其他非纯白色,就选“白底”分类卡片;如果卡片背景环境是纯白色,就选“灰底”分类卡片。不要让 Card 和父级背景同色相贴。",
1102
1102
  "【状态】白底卡默认态使用 65% 白底 + 轻白描边;hover 后补满白底并出现业务卡片专用投影;灰底卡保持灰底与灰描边并保留投影反馈",
1103
1103
  "【指标】指标项推荐控制在 3 项以内,图标 14px、文字 12px,项间距 20px",
1104
1104
  "【标签】通用 tags 只用于数据卡片,展示在左下角并与右侧操作按钮水平对齐;商品卡片和信息卡片1禁止渲染通用 tags,主标题左侧不得出现“标签”等前缀标签,运行时会隐藏误注入的 white Tag。Card 内所有标签默认必须使用圆角矩形样式 `radius=\"md\"`,不使用全圆胶囊 `radius=\"full\"`;数据卡片、商品状态标签、信息卡片1徽标都遵守该规则。数据卡片不论是否展示图标、也不论 dataIconStyle 为 tone 或 inverse,默认标签都必须统一使用 grey 标签样式。商品状态标签和信息卡片1徽标仅可展示在右上角并与标题水平对齐。信息卡片1徽标与 infoIconStyle 联动:`tone` 时一律 grey,`inverse` 时默认彩色 purple;卡片彩色标签优先使用 purple(紫色)/ teal(青绿色)/ blue(蓝色)/ cyan(青色)/ orange(橙色),避免优先使用 pink、red、yellow 等过强警示或装饰色。商品状态标签复用 green + l + md;不显示图标和关闭按钮,建议使用 2-4 字短标签。",
@@ -1546,7 +1546,7 @@
1546
1546
  "kind": "component",
1547
1547
  "name": "InfoDisplayPanel",
1548
1548
  "category": "business",
1549
- "description": "信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并可按当前选中 tab 拆分出独立栏,最多支持 3 栏。",
1549
+ "description": "信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并按业务动态 tab1 / tab2 / tab3 拆分为 1-3 栏,支持整体宽度与栏间宽度拖拽。",
1550
1550
  "import": {
1551
1551
  "from": "@tfdesign/b-end",
1552
1552
  "default": "InfoDisplayPanel"
@@ -1623,6 +1623,7 @@
1623
1623
  "【不适用场景】整页客服工作台外框用 CustomerServiceWorkspaceFrame;左侧会话 / 工单队列用 ConversationList;单个业务摘要用 Card;字段型详情或可批量操作数据用 Form / Table。",
1624
1624
  "【结构】外层为一个白色圆角容器,默认使用 `w-full` 撑满父容器可用宽度;内部按“拆分栏数组 + 主栏”组织。每一栏都必须包含 56px 顶部 Tabs 栏和内容区;顶部横线只保留 Header 内 1 条通栏下描边,左右贴满当前栏容器。Tabs 交互、文字、选中态与 3px Brand 指示线必须复用基础 `Tabs variant=\"line\" size=\"lg\"`,禁止手写 tab button 或手写选中线。栏与栏之间用 1px 垂直分隔线,禁止通过 gap 制造分栏空隙。",
1625
1625
  "【分栏能力】默认单栏时,全部 tabs 位于主栏;主栏永远在最右侧,拆分出的独立栏固定追加在主栏左侧。每栏右上角始终只有 1 个动作按钮,拆分与合并统一使用 `Button variant=\"ghost-black\" size=\"md\" iconOnly tooltip=\"拆分/合并\"` 显示提示文案。最多支持 3 栏,即 1 个主栏 + 最多 2 个拆分栏。",
1626
+ "【动态 tab1/tab2/tab3 拆分】InfoDisplayPanel 必须按业务实际 tabs 动态决定可见栏数,而不是写死三栏:只有 1 个分类时自动降级为单栏标题 + 内容;有 2 个分类时最多支持 2 栏;有 3 个及以上分类且容器宽度达到三栏门槛时才支持 3 栏。tab 的数量、名称、顺序、内容都必须来自 `panels[].tabs` / `panels[].content` 或 `renderPanelContent`,不要把“托管助手 / 历史工单 / 工单日志”等示例固定写死到所有业务页面。",
1626
1627
  "【Tabs来源】Tabs 的数量、名称和顺序必须跟随具体生成页面的业务信息架构动态生成,由 `panels[].tabs` 提供;不要把默认示例 tabs 当作真实业务规则,也不要为了凑满 3 栏强行生成无意义的 tab。",
1627
1628
  "【Tab数量与拆分上限】可拆栏数同时受容器宽度和 tab 总数限制:仅 1 个 tab 时不使用 Tabs,运行时自动降级为 `FormTitle variant=\"card\"` 标题展示且不显示拆分按钮;仅 2 个 tab 时最多拆分为 2 栏;大于等于 3 个 tab 且容器宽度达到 602px 时才允许拆分为 3 栏。",
1628
1629
  "【拆分规则】点击主栏右上角“拆分”时,只拆出当前选中的主栏 tab;拆分后该 tab 从主栏 tabs 中移除,并以独立栏形式出现在主栏左侧。主栏保留剩余未拆分 tabs,顺序始终保持传入时的原始顺序。",
@@ -1639,6 +1640,7 @@
1639
1640
  "【组合关系】InfoDisplayPanel 可作为 CustomerServiceWorkspaceFrame 的 mainPanel 内容;放入主白卡时宽高使用 `size-full` 或父容器约束,外层不要再套一层大白卡。",
1640
1641
  "【嵌入客服工作台时的语义】在客服工作台框架模版里,InfoDisplayPanel 不是独立静态侧栏,而是“当前左侧选中会话 / 工单”的辅助信息区。左侧会话列表默认选中项变化时,InfoDisplayPanel 内各 tab 的内容也必须同步切换到该当前对象对应的数据上下文,例如用户信息、历史工单、工单日志、视频信息、沟通记录都应属于同一个当前处理对象。",
1641
1642
  "【联动边界】客服工作台里的右侧白卡应被视为一个整体工作区:左侧 IM 对话区负责当前线程,右侧 InfoDisplayPanel 负责同一线程的辅助信息。两者由外层模板统一接收当前对象 id 并同步刷新;InfoDisplayPanel 不应自己脱离左侧上下文单独维持另一份“当前对象”。",
1643
+ "【嵌入客服工作台时的能力保留】当 InfoDisplayPanel 被客服工作台框架使用时,必须保留整体信息栏宽度拖拽、内部栏间拖拽、动态 tab 拆分 / 合并、单 tab 降级、宽度门槛和最小栏宽保护。AI 生成页面不得把 InfoDisplayPanel 替换成静态 Card 列、普通 Tabs 面板或固定宽 div,也不得删除拖拽热区和拆分按钮。",
1642
1644
  "【可控状态】业务需要同步 URL / 埋点 / 右侧主区状态时,使用 `activeTabs + onTabChange` 控制每栏当前 tab,并使用 `onSplitChange` 感知当前拆分出来的 tab 列表与实际栏数;`columnCount` / `defaultColumnCount` 仅保留为兼容字段,不再作为主交互入口推荐,默认最大栏数按组件逻辑自动支持到 3 栏。"
1643
1645
  ],
1644
1646
  "examples": [
@@ -7143,6 +7145,7 @@
7143
7145
  "【顶部工具】指标区右侧按钮仅用于平台框架级入口,例如全局设置、平台公告、平台数据统计等;最多展示 3 个按钮,超出时应按平台级优先级裁切;按钮必须复用 `Button iconOnly` 并提供 `tooltip` / `aria-label`,不要放置页面内局部操作或业务表单操作。",
7144
7146
  "【工作区层级】Workspace 外层只负责横向布局、覆盖层级与拖拽,不承担可见圆角;左侧板块自身为 `bg-white/50 + border-white + rounded-xl`,右侧主面板自身为白色 `surface + border-white + rounded-xl`,两者高度一致、宽度不同,并通过主面板 `margin-left: -32px` 形成“右侧白板覆盖在左侧灰板上”的叠压视觉,而不是中间留缝的并排关系。",
7145
7147
  "【可见圆角】最终可见的 6 个角统一为 16px:左侧板块左上 / 左下两个角露出;右侧主白卡四角露出;左侧板块右上 / 右下两个角必须被右侧主白卡完整覆盖。主面板覆盖量必须大于圆角半径(推荐 32px = 2 × 16px),防止主面板左上 / 左下圆角切角处露出左侧板块的右边界或描边,避免出现“残缺一块”的视觉。",
7148
+ "【框架级能力不可删减】当 AI 生成页面使用客服工作台框架时,必须完整保留框架级交互能力:左侧列表 / 卡片容器宽度拖拽、左侧与右侧主白卡之间的覆盖式拖拽、右侧 InfoDisplayPanel 整体宽度拖拽、InfoDisplayPanel 内部栏间拖拽、响应式最小宽度保护、自动折叠、动态分栏和上下文同步。禁止只复刻截图外观却删除拖拽条、键盘微调、宽度下限、自动适配或 tab 拆分逻辑。",
7146
7149
  "【左右拖拽】左侧区域与右侧主白卡之间必须提供纵向拖拽热区,默认热区 8px;拖拽热区覆盖在主面板左边缘上,不占用布局宽度、不制造中间空隙;拖动时调整左侧区域宽度并让右侧主面板 `flex-1 min-w-0` 自动占满剩余空间;左侧默认宽度 432px(400px 可视会话列表 + 32px 主面板覆盖区);拖拽最小宽度优先由左侧业务组件最小可用宽度决定,例如 ConversationList 默认列表纯头像锁定 88px 时,左侧板块最小宽度为 `max(100px 框架兜底, 88px + 32px 覆盖区) = 120px`;ConversationList 卡片列表最小内容宽度为 333px,左侧板块最小宽度为 365px;右侧纯白主容器最小宽度 380px,左侧最大可拖宽度需按 `工作区宽度 + 32px 覆盖量 - 380px` 动态计算;拖拽条需支持键盘左右方向键微调。",
7147
7150
  "【左侧插槽】左侧半透明容器默认承载平台业务组件 `ConversationList`,用于会话 / 工单 / 托管队列切换;左侧内容区必须扣除右侧 32px 覆盖安全区,避免被主白卡裁切;`ConversationList` 展开态四周内容间距固定 16px。嵌入时 `ConversationList` 必须设置 `resizable={false}`、保留 `collapsible` 与 `autoCollapseOnNarrow`、`style={{ width: \"100%\", height: \"100%\" }}`,并在外层框架接入 `onLayoutWidthRequest` 与 `onVariantChange`:默认列表传 `leftContentMinWidth={88}`,卡片列表切换为 `leftContentMinWidth={333}`。框架统一管理宽度拖拽,但拖拽下限跟随子组件能力;点击会话列表左上角收起按钮时,外层左侧半透明容器必须同步吸附缩窄到 120px,不允许只把 ConversationList 缩到头像列而外层仍保留大片空白。",
7148
7151
  "【左侧选中项 = 右侧上下文源】客服工作台框架内左侧 `ConversationList` 默认必须存在一个当前选中项;它不是单纯的导航高亮,而是整个右侧主白卡的上下文源。切换左侧会话 / 工单 / 托管项时,右侧纯白容器里的 IM 对话区和 InfoDisplayPanel 信息区都必须同步切换到同一个当前处理对象,禁止出现“左侧已切到 B 会话,但右侧聊天仍是 A、信息区还是 C”的上下文错位。预览实现必须以 `activeConversationId` 作为单一事实源:左侧列表头像、标题、单号与右侧 IM Header 的头像、标题、会话 ID、渠道、沟通时长和消息脚本必须一一对应,不能只更新高亮而复用固定聊天剧本。",
@@ -7152,6 +7155,8 @@
7152
7155
  "【右侧纯白卡语义】右侧纯白色主卡定义的是“当前处理对象的完整工作区”,不是两个彼此独立的小组件容器。IMConversationPattern 负责当前线程消息往返,InfoDisplayPanel 负责同一对象的辅助信息、历史工单、日志、视频信息、用户信息等;两者必须共享同一个当前会话 / 工单 / 线程上下文。",
7153
7156
  "【状态同步原则】当业务接真实数据、URL 或埋点时,应以左侧当前选中对象 id 作为单一事实源(single source of truth),再派生右侧聊天流和信息面板内容;不要让 IMConversationPattern 和 InfoDisplayPanel 各自维护独立当前对象状态。",
7154
7157
  "【右侧信息栏整体拖拽】在客服工作台框架模版里,右侧 InfoDisplayPanel 必须支持整体宽度拖拽,拖拽热区位于信息栏左边界,默认热区 8px;拖拽时改变的是右侧信息栏整体宽度,左侧 IM 聊天区自动占满剩余空间,而不是改由 InfoDisplayPanel 本体处理页面级宽度。推荐默认宽度约 380px,最小宽度 320px,并保证左侧 IM 区至少保留 360px 可读宽度;同时保留 InfoDisplayPanel 内部相邻栏之间的原生拖拽能力。",
7158
+ "【InfoDisplayPanel 动态 tab 拆分】右侧信息展示区必须保留 InfoDisplayPanel 的动态 tab1 / tab2 / tab3 栏拆分能力:业务只有 1 个分类时降级为单栏标题 + 内容;业务有 2 个分类时最多支持拆成 2 栏;业务有 3 个及以上分类且宽度达到三栏门槛时支持拆成 3 栏。tab 数量、名称、顺序和内容必须来自当前会话 / 工单上下文的业务数据,不允许固定写死 3 个静态按钮,也不允许用多个 Card 或 div 手搓三栏替代。",
7159
+ "【拖拽与拆分保留验收】客服工作台生成页必须同时满足:拖动左侧列表边界时 ConversationList 默认列表 / 卡片列表容器实时适配;拖动右侧 InfoDisplayPanel 左边界时信息栏整体宽度变化且 IM 区自动吃剩余空间;拆出 InfoDisplayPanel tab 后,栏间分隔线可继续调节相邻栏宽,且每栏不小于 200px。任一能力缺失都视为没有正确使用客服工作台框架。",
7155
7160
  "【右侧插槽】如业务需要替换右侧主内容,可放 ChatConversationPattern、Table、表单详情或工单处理面板;但客服接待类页面优先保持“IM 对话 + InfoDisplayPanel”的组合,不要用多个普通 Card 临时拼出右侧信息区。",
7156
7161
  "【AI 选型】当 prompt 出现“客服工作台框架 / 客服工作台 / 客服在线工作台 / 在线客服工作台 / 在线 Agent / Agent 工作台 / 基础模式 / 托管模式 / 客服名称在线状态 / 顶部指标工具条”等客服工作台信号时,优先选本页面模板;但本模板右侧主内容仍必须包含 IMConversationPattern 聊天区和 InfoDisplayPanel 信息区。如果只是全局左侧导航,选 NavBar;如果只是会话队列,选 ConversationList;如果只是单条消息,选 ChatMessage / ChatBubble。",
7157
7162
  "【容器语义】本模板自身已经包含浅灰页面底与右侧主白卡,生成页面时不要外层再套 `Card color=\"grey\"`、`bg-surface rounded-xl` 或大白卡 section,否则会形成“灰底 + 大白卡 + 框架”的错误嵌套。",
@@ -7323,10 +7328,10 @@
7323
7328
  "【框架不动】外框灰底 + 左侧 `NavBar` 固定不变;右侧内容区必须 `flex-1 min-w-0 min-h-0 overflow-y-auto overflow-x-hidden`,由右侧内容区作为唯一滚动容器承载整体页面滚动;Hero、ChatInput、筛选行、卡片网格要一起被滑动,禁止只让下方卡片列表单独 `overflow-y-auto`。",
7324
7329
  "【Hero 区】居中欢迎标题 + 最大宽 680px 的 ChatInput 直接坐浅灰底;Hero 主标题使用 `text-4xl leading-10`;默认使用 `ChatInput variant=\"default\"` 作为入口主输入;标准节奏为 `padding: 84px 40px 80px`,标题区较旧版整体上移 36px。标题区副标题与 ChatInput 之间必须保留 48px 间距(比普通标题说明节奏额外增加 24px),保证输入框不贴近标题区。AI 渐变只用于输入框内部 / AI 标识,不作为整块 Hero 白底。",
7325
7330
  "【筛选与分类】分类切换必须用 `Tabs size=\"sm\"`;筛选行 `padding: 16px 40px`,左 Tabs、右搜索。模板/助手搜索框是辅助筛选入口,不是页面主输入,必须固定宽度并自动对齐到页面右侧:可用外层 `<div className=\"ml-auto shrink-0\" style={{ flex: \"0 0 240px\", width: \"240px\", minWidth: \"240px\", maxWidth: \"240px\" }}>` 包裹 `Input`,也可直接给 `Input` 传 `style={{ flex: \"0 0 240px\", \"--size-input-width\": \"240px\" }}`;`Input` 的 `style` 作用于根容器。Tabs 外层使用 `flex shrink-0` 保持左侧胶囊 Tabs 可见。筛选行容器禁止使用 `flex-wrap`、`justify-between`、`space-y-*`、`flex-col`;禁止让筛选搜索框 `flex-1`、`w-full`、撑满 Tabs 右侧剩余空间,或换到 Tabs 下方单独占一行。",
7326
- "【卡片列表】使用自适应 grid(优先 `repeat(auto-fit, minmax(min(320px, 100%), 1fr))`),网格 `gap-4`,禁止固定列宽导致窄屏横向溢出;卡片使用 `Card color=\"white\"`;列表区域作为普通内容块跟随右侧页面整体滚动,使用 `shrink-0` + `padding: 0 40px 40px`,禁止在卡片列表自身设置 `overflow-y-auto` 造成只有卡片区滚动。",
7331
+ "【卡片列表】使用自适应 grid(优先 `repeat(auto-fit, minmax(min(320px, 100%), 1fr))`),网格 `gap-4`,禁止固定列宽导致窄屏横向溢出;当前列表直接坐浅灰页面底,所以卡片统一使用 `Card color=\"white\"`。通用铁律是:纯白容器里必须改用 `color=\"grey\"`,灰色/浅灰/其他非纯白容器里必须使用 `color=\"white\"`,且这条规则对所有 Card 分类都生效;列表区域作为普通内容块跟随右侧页面整体滚动,使用 `shrink-0` + `padding: 0 40px 40px`,禁止在卡片列表自身设置 `overflow-y-auto` 造成只有卡片区滚动。",
7327
7332
  "【内容可扩展】Tab 项数量和卡片数据替换为业务真实数据;卡片 stats/tags 按业务维度自定义"
7328
7333
  ],
7329
- "code": "import NavBar from './components/NavBar';\nimport Tabs from './components/Tabs';\nimport ChatInput from './components/ChatInput';\nimport Card from './components/Card';\nimport Icon from './components/Icon';\nimport Input from './components/Input';\n\nconst CATEGORY_TABS = [\n { label: '全部' },\n { label: '推荐', icon: <Icon name=\"star-01-stroked\" /> },\n { label: '对话助手', icon: <Icon name=\"message-chat-square-stroked\" /> },\n { label: '数据分析', icon: <Icon name=\"bar-chart-01-stroked\" /> },\n];\n\nconst TEMPLATE_SEARCH_WRAP_STYLE = {\n flex: '0 0 240px',\n width: '240px',\n minWidth: '240px',\n maxWidth: '240px',\n};\n\nexport default function ChatHomePage() {\n return (\n <div\n className=\"flex w-full items-stretch overflow-hidden\"\n style={{\n flex: '1 0 auto', alignSelf: 'stretch', minHeight: '720px',\n background: 'var(--color-blueGrey-200)',\n borderRadius: 0,\n }}\n >\n <div className=\"flex shrink-0\">\n <NavBar platform=\"ola\" selectedItemId=\"knowledge\" />\n </div>\n\n <div className=\"flex flex-1 min-w-0 min-h-0 flex-col overflow-y-auto overflow-x-hidden\">\n {/* Hero 区:欢迎语 + ChatInput */}\n <div\n className=\"flex flex-col items-center shrink-0\"\n style={{\n padding: '84px 40px 80px',\n }}\n >\n <div className=\"flex flex-col items-center gap-2 w-full text-center\">\n <h1 className=\"m-0 text-4xl [font-weight:var(--font-semibold)] leading-10\" style={{ color: 'var(--foreground)' }}>\n 今天想做什么?\n </h1>\n <p className=\"m-0 text-sm\" style={{ color: 'var(--foreground-muted)' }}>\n 从下方搜索助手,或直接输入你的需求,AI 帮你快速搞定\n </p>\n </div>\n <div className=\"w-full\" style={{ maxWidth: '680px', marginTop: '48px' }}>\n <ChatInput variant=\"default\" placeholder=\"描述你想完成的任务…\" />\n </div>\n </div>\n\n {/* 筛选行:Tabs + 固定宽搜索,直接坐灰底 */}\n <div\n className=\"flex min-w-0 shrink-0 items-center gap-4\"\n style={{ padding: '16px 40px' }}\n >\n <div className=\"flex shrink-0\">\n <Tabs variant=\"pill\" size=\"sm\" items={CATEGORY_TABS} defaultIndex={0} />\n </div>\n <div className=\"ml-auto shrink-0\" style={TEMPLATE_SEARCH_WRAP_STYLE}>\n <Input\n placeholder=\"搜索标题、描述\"\n prefix={<Icon name=\"search-md-stroked\" size=\"sm\" />}\n allowClear\n />\n </div>\n </div>\n\n {/* 卡片列表:随右侧页面整体滚动,卡片自身是白卡,外层不包白卡 */}\n <div className=\"shrink-0\" style={{ padding: '0 40px 40px' }}>\n <div className=\"grid gap-4\" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(min(320px, 100%), 1fr))' }}>\n {/* 👉 替换为业务卡片数据 */}\n <Card color=\"white\" title=\"智能客服助手\" description=\"基于大模型的全渠道客服对话助手\" tags={['客服', '对话']} />\n <Card color=\"white\" title=\"数据洞察报告\" description=\"上传数据,自动输出可视化洞察报告\" tags={['数据', '分析']} />\n <Card color=\"white\" title=\"知识库 QA 问答\" description=\"基于私有知识文档,快速搭建专属问答\" tags={['知识库', 'RAG']} />\n </div>\n </div>\n </div>\n </div>\n );\n}",
7334
+ "code": "import NavBar from './components/NavBar';\nimport Tabs from './components/Tabs';\nimport ChatInput from './components/ChatInput';\nimport Card from './components/Card';\nimport Icon from './components/Icon';\nimport Input from './components/Input';\n\nconst CATEGORY_TABS = [\n { label: '全部' },\n { label: '推荐', icon: <Icon name=\"star-01-stroked\" /> },\n { label: '对话助手', icon: <Icon name=\"message-chat-square-stroked\" /> },\n { label: '数据分析', icon: <Icon name=\"bar-chart-01-stroked\" /> },\n];\n\nconst TEMPLATE_SEARCH_WRAP_STYLE = {\n flex: '0 0 240px',\n width: '240px',\n minWidth: '240px',\n maxWidth: '240px',\n};\n\nexport default function ChatHomePage() {\n return (\n <div\n className=\"flex w-full items-stretch overflow-hidden\"\n style={{\n flex: '1 0 auto', alignSelf: 'stretch', minHeight: '720px',\n background: 'var(--color-blueGrey-200)',\n borderRadius: 0,\n }}\n >\n <div className=\"flex shrink-0\">\n <NavBar platform=\"ola\" selectedItemId=\"knowledge\" />\n </div>\n\n <div className=\"flex flex-1 min-w-0 min-h-0 flex-col overflow-y-auto overflow-x-hidden\">\n {/* Hero 区:欢迎语 + ChatInput */}\n <div\n className=\"flex flex-col items-center shrink-0\"\n style={{\n padding: '84px 40px 80px',\n }}\n >\n <div className=\"flex flex-col items-center gap-2 w-full text-center\">\n <h1 className=\"m-0 text-4xl [font-weight:var(--font-semibold)] leading-10\" style={{ color: 'var(--foreground)' }}>\n 今天想做什么?\n </h1>\n <p className=\"m-0 text-sm\" style={{ color: 'var(--foreground-muted)' }}>\n 从下方搜索助手,或直接输入你的需求,AI 帮你快速搞定\n </p>\n </div>\n <div className=\"w-full\" style={{ maxWidth: '680px', marginTop: '48px' }}>\n <ChatInput variant=\"default\" placeholder=\"描述你想完成的任务…\" />\n </div>\n </div>\n\n {/* 筛选行:Tabs + 固定宽搜索,直接坐灰底 */}\n <div\n className=\"flex min-w-0 shrink-0 items-center gap-4\"\n style={{ padding: '16px 40px' }}\n >\n <div className=\"flex shrink-0\">\n <Tabs variant=\"pill\" size=\"sm\" items={CATEGORY_TABS} defaultIndex={0} />\n </div>\n <div className=\"ml-auto shrink-0\" style={TEMPLATE_SEARCH_WRAP_STYLE}>\n <Input\n placeholder=\"搜索标题、描述\"\n prefix={<Icon name=\"search-md-stroked\" size=\"sm\" />}\n allowClear\n />\n </div>\n </div>\n\n {/* 卡片列表:当前直接坐浅灰页面底,所以卡片统一使用白底 Card;若未来改成纯白容器,必须同步切换为 grey 灰底卡 */}\n <div className=\"shrink-0\" style={{ padding: '0 40px 40px' }}>\n <div className=\"grid gap-4\" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(min(320px, 100%), 1fr))' }}>\n {/* 👉 替换为业务卡片数据 */}\n <Card color=\"white\" title=\"智能客服助手\" description=\"基于大模型的全渠道客服对话助手\" tags={['客服', '对话']} />\n <Card color=\"white\" title=\"数据洞察报告\" description=\"上传数据,自动输出可视化洞察报告\" tags={['数据', '分析']} />\n <Card color=\"white\" title=\"知识库 QA 问答\" description=\"基于私有知识文档,快速搭建专属问答\" tags={['知识库', 'RAG']} />\n </div>\n </div>\n </div>\n </div>\n );\n}",
7330
7335
  "keywords": [
7331
7336
  "ChatHomePagePattern",
7332
7337
  "AI 入口页",
@@ -7447,9 +7452,10 @@
7447
7452
  "rules": [
7448
7453
  "【五模板定位】本模板只用于“AI 任务会话 / AI 助手会话”的持续协作页:用户第一步是继续追问、确认规划、查看流式执行、查看 AI 产物卡或 follow-up。若用户还没开始任务,用 `ChatHomePagePattern`;若右侧主产物/主编辑区才是页面主角,AI 只是侧助,用 `CopilotPagePattern`;若是真人客服/私信/站内信回复,用 `IMConversationPattern`;若是管理一批会话/任务列表,用 `BasePageFramePattern`。",
7449
7454
  "【布局 Recipe】必须按 `LAYOUT_RECIPES.md` 选择 `chat-conversation`:消息流和底部输入是主工作流,滚动归属消息流;底部 `ChatInput` 固定吸底,不随消息滚动。",
7450
- "【组件选型门禁】所有消息必须用 `ChatMessage`:用户消息用 `role=\"user\"`,AI 消息用 `header` 并按需要组合 `thinking` / `plan` / `taskGroups` / `resultText` / `resultArtifacts` / `followUps`;底部输入必须用 `ChatInput`;顶部和消息操作用 `Button` + `Icon`;欢迎/空会话态可用 `Empty` 或模板内欢迎屏。禁止用 `ChatBubble`、Card 或裸 div 模拟 AI 消息。",
7455
+ "【组件选型门禁】所有消息必须用 `ChatMessage`:用户消息用 `role=\"user\"`,AI 消息用 `header` 并按需要组合 `thinking` / `plan` / `taskGroups` / `resultText` / `resultArtifacts` / `followUps`;底部输入必须用 `ChatInput`;顶部和消息操作用 `Button` + `Icon`;空会话必须使用模板内置的「新建会话页」,不要退回 `Empty`、空白消息区或自制欢迎卡。禁止用 `ChatBubble`、Card 或裸 div 模拟 AI 消息。",
7451
7456
  "【整体框架】外框灰底(color-blueGrey-200);不带左侧 NavBar,整页就是一个独立会话工作区;不嵌中间白卡,顶导栏 / 消息区 / ChatInput 三层都直接坐灰底上;业务页面根容器不额外加外圈圆角或描边",
7452
7457
  "【顶导栏】shrink-0,左侧 ghost-black 返回箭头 + 会话标题(truncate),右侧按权重递增:share-07 / 会话详情 outline-black / 更多 dots / 新会话 primary(主操作唯一固定最右)",
7458
+ "【顶导栏图标按钮】顶导栏内所有仅图标操作必须使用 `Button iconOnly tooltip=\"操作文案\"`,hover/focus 时展示 Tooltip,且 tooltip 为字符串时由 Button 自动补 `aria-label`;禁止只传 `aria-label` 或只放 Icon 让用户猜操作。",
7453
7459
  "【布局结构】flex h-full flex-col:① 顶导栏 shrink-0 ② 中部消息区 flex-1 + min-h-0 + overflow-y-auto 独立内滚 ③ ChatInput shrink-0 吸底;不要给中部加白卡背景",
7454
7460
  "【宽度规格】对话流和 ChatInput 都用 800px + mx-auto 居中;对话流相对 ChatInput 左右各缩进 12px(消息区 padding \"0 20px\",ChatInput padding \"0 8px\"),让消息看起来比输入框窄一圈",
7455
7461
  "【消息组件统一】所有消息(用户 / AI)一律用 ChatMessage 渲染。用户消息 role=\"user\" + userContent / userAttachments;AI 消息 header=true + 任意组合 thinking / leadText / plan / taskGroups / resultText / resultArtifacts / followUps。禁止用 ChatBubble 渲染 AI 回复",
@@ -7460,7 +7466,7 @@
7460
7466
  "【取消链路】用户点任务规划卡的「取消」:1) 卡片变 confirmed 置灰 2) 追加用户消息「取消」 3) 600ms 后追加 AI 短答寒暄(\"已取消任务,还有其他问题随时找我~\")",
7461
7467
  "【追问交互】followUps 字符串数组在父组件 wrap 成 { items, onSelect: onSend } 形式,点击 chip 必须等价于立即发送该文本:先追加一条右侧 user 气泡,再由 AI 追加对应的新答复 / 产物 / 下一组 followUps;不要只回填输入框等待用户二次发送",
7462
7468
  "【自动滚底】用 ResizeObserver 监听消息列表 firstElementChild 的高度变化,任何高度增长都 scrollTo({ top: scrollHeight, behavior: smooth });覆盖新消息追加 / 流式 step / 折叠展开 三种触发场景,比单独 useEffect([messages]) 更稳",
7463
- "【欢迎屏】点「新会话」→ phase 切到 welcome:清空 messages,中部居中展示 CATCAT 头像(66px 渐变描边圆 + 蓝色投影 + 32px catcat.svg)+「OLA AI」标题 + 欢迎语 + 3 条推荐 chip(白底 0.6 透明 + inset 1px 白边 + 右箭头);ChatInput 仍 shrink-0 吸底(位置和 chat 阶段保持一致),placeholder 改成\"需要我为你做什么…\";用户在欢迎屏发送或点 chip → phase 自动切回 chat 走完整链路",
7469
+ "【新建会话页 = 空对话唯一标准态】只要当前会话没有消息(初次进入空会话、外部传入 `messages=[]`、或用户点击「新会话」清空消息),都必须展示统一的新建会话页:中部居中展示 CATCAT 头像(66px 渐变描边圆 + 蓝色投影 + 32px catcat.svg)+「OLA AI」标题 + 欢迎语 + 3 条推荐 chip(白底 0.6 透明 + inset 1px 白边 + 右箭头);底部 `ChatInput` 仍 shrink-0 吸底,placeholder 固定为\"需要我为你做什么…\";禁止出现空白消息区、默认 mock 对话残留或与图示不一致的空状态。",
7464
7470
  "【关键词路由】① 含「整理 / 分析 / 生成 / 梳理 / 输出 / 汇总」→ 任务规划卡链路(待用户点开始/取消) ② 含「停止 / 取消」→ AI 短答「已停止当前任务,需要时再叫我。」 ③ 其他 → AI 短答从备选句池随机一句,避免机械化",
7465
7471
  "【任务规划卡 defaultConfirmed】历史消息(mock 中的已处理卡片)通过 plan.defaultConfirmed=true 让卡片初始就处于禁用置灰态;TaskPlanCard 内部用 useState(plan.defaultConfirmed === true) 初始化 confirmed,并用 useEffect 同步 props 变化"
7466
7472
  ],
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "system": "b-end",
3
3
  "skill": "tfds",
4
- "generatedAt": "2026-05-12T13:53:18.654Z",
4
+ "generatedAt": "2026-05-13T05:01:47.099Z",
5
5
  "purpose": "轻量组件与页面模板目录。AI 先读本文件做选型;确定命中组件后,再到 components.index.json 按 id 读取 props / rules / examples。",
6
6
  "counts": {
7
7
  "total": 47,
@@ -231,7 +231,7 @@
231
231
  "kind": "component",
232
232
  "name": "Card",
233
233
  "category": "business",
234
- "description": "业务信息摘要卡片,支持数据卡片、商品卡片、信息卡片1和信息卡片2。颜色使用遵循背景反衬原则:灰色背景用白底卡,白色背景用灰底卡。",
234
+ "description": "业务信息摘要卡片,支持数据卡片、商品卡片、信息卡片1和信息卡片2。所有卡片分类统一遵循背景反衬原则:灰色/浅灰/非纯白背景用白底卡,纯白背景用灰底卡。",
235
235
  "import": {
236
236
  "from": "@tfdesign/b-end",
237
237
  "default": "Card"
@@ -313,7 +313,7 @@
313
313
  "kind": "component",
314
314
  "name": "InfoDisplayPanel",
315
315
  "category": "business",
316
- "description": "信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并可按当前选中 tab 拆分出独立栏,最多支持 3 栏。",
316
+ "description": "信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并按业务动态 tab1 / tab2 / tab3 拆分为 1-3 栏,支持整体宽度与栏间宽度拖拽。",
317
317
  "import": {
318
318
  "from": "@tfdesign/b-end",
319
319
  "default": "InfoDisplayPanel"
@@ -344,7 +344,7 @@
344
344
  "多面板工作区",
345
345
  "panel tabs"
346
346
  ],
347
- "ruleCount": 26,
347
+ "ruleCount": 28,
348
348
  "exampleCount": 8,
349
349
  "hasCode": false,
350
350
  "detailRef": "components.index.json#info-display-panel"
@@ -1634,7 +1634,7 @@
1634
1634
  "工单处理框架",
1635
1635
  "CustomerServiceWorkspaceFrame"
1636
1636
  ],
1637
- "ruleCount": 22,
1637
+ "ruleCount": 25,
1638
1638
  "exampleCount": 0,
1639
1639
  "hasCode": true,
1640
1640
  "detailRef": "components.index.json#customer-service-workspace-frame"
@@ -1798,7 +1798,7 @@
1798
1798
  "流式执行",
1799
1799
  "任务规划卡"
1800
1800
  ],
1801
- "ruleCount": 18,
1801
+ "ruleCount": 19,
1802
1802
  "exampleCount": 0,
1803
1803
  "hasCode": true,
1804
1804
  "detailRef": "components.index.json#chat-conversation-page"
@@ -284,7 +284,7 @@ const ACTION = [
284
284
  * @prop {'data'|'product'|'info'|'info2'|'animated'} [type='data'] — 卡片类型:data 为数据卡片,product 为商品卡片,info 为信息卡片1,info2 为信息卡片2;animated 为旧版兼容别名
285
285
  * @prop {string} [title] — 卡片标题,商品卡片中为商品标题
286
286
  * @prop {string} [description] — 卡片描述,商品卡片中为数量、价格等辅助信息
287
- * @prop {'white'|'grey'} [color='white'] — 卡片颜色:white 为白底默认样式,grey 为 Blue Grey 灰底样式
287
+ * @prop {'white'|'grey'} [color='white'] — 卡片颜色:white 为白底样式,适用于灰/浅灰/非纯白容器;grey 为 Blue Grey 灰底样式,适用于纯白容器
288
288
  * @prop {Array<{iconName: string, value: string, tooltip?: string}>|null} [stats=null] — 数据卡片指标数组,最多展示 3 项;tooltip 用于 hover/focus 展示详细说明
289
289
  * @prop {string[]|null} [tags=null] — 数据卡片标签数组,建议 1-2 个短标签;商品卡片和信息卡片1不渲染通用 tags
290
290
  * @prop {string} [productStatus='已使用'] — 商品状态标签文案
@@ -314,7 +314,8 @@ const ACTION = [
314
314
  * @prop {object} [style] — 内联样式
315
315
  *
316
316
  * 通用 tags 仅用于数据卡片左下角;商品和信息卡片1不渲染标题左侧通用标签。
317
- * 卡片容器默认半透明白底,hover 后补满白底并出现业务卡片专用投影。
317
+ * 所有 Card 分类都遵循“背景反衬”规则:父容器是纯白色时必须使用灰底卡;父容器是灰色、浅灰色或其他非纯白底时必须使用白底卡。
318
+ * 卡片容器默认半透明白底,hover 后补满白底并出现业务卡片专用投影;灰底卡保持灰底与灰描边,并保留相同投影反馈。
318
319
  */
319
320
  export default function Card({
320
321
  type = 'data',
@@ -353,6 +354,7 @@ export default function Card({
353
354
  const resolvedType = type === 'product' || type === 'info'
354
355
  ? type
355
356
  : (type === 'info2' || type === 'animated' ? 'info2' : 'data');
357
+ // Card 颜色依赖父级背景做反衬,不按卡片类型区分。
356
358
  const resolvedColor = color === 'grey' ? 'grey' : 'white';
357
359
  const resolvedTitle = title || (
358
360
  resolvedType === 'product'
@@ -116,6 +116,8 @@ export const CARD_TOKEN_MAP = {
116
116
  { label: '操作文案', cssProp: 'a11y-label', value: 'animatedActionText 作为圆形箭头按钮语义文案,不直接渲染为文字按钮' },
117
117
  ],
118
118
  卡片: [
119
+ { label: '容器映射规则', cssProp: 'usage', value: '所有卡片分类统一按父容器背景反衬:纯白容器用 grey 灰底卡;灰色 / 浅灰 / 其他非纯白容器用 white 白底卡' },
120
+ { label: '规则适用范围', cssProp: 'scope', value: '数据卡片 / 商品卡片 / 信息卡片1 / 信息卡片2 全部生效,不因卡片类型变化' },
119
121
  { label: '白底背景', cssProp: 'background', token: '--color-card-secondary', value: 'rgba(255,255,255,0.65)', semanticRef: 'bg-card-secondary', state: 'default' },
120
122
  { label: '白底 Hover 背景', cssProp: 'background', token: '--color-surface', value: '#FFFFFF', semanticRef: 'bg-surface', state: 'hover' },
121
123
  { label: '白底描边', cssProp: 'border-color', token: '--color-white', value: '#FFFFFF' },
@@ -499,6 +499,7 @@ function ConversationCardFooter({ card, onSend }) {
499
499
  icon={<Icon name="send-01-stroked" size={16} aria-hidden="true" />}
500
500
  iconOnly
501
501
  className="!h-6 !w-6 !rounded-md !p-1"
502
+ tooltip="发送回复"
502
503
  onKeyDown={(event) => {
503
504
  event.stopPropagation();
504
505
  }}
@@ -1049,17 +1050,16 @@ function ConversationListDefaultVariant({
1049
1050
  {!isCardVariant && isAvatarOnly ? (
1050
1051
  <>
1051
1052
  <div className={AVATAR_ONLY_HEADER}>
1052
- <Tooltip content="展开会话列表">
1053
- <Button
1054
- type="button"
1055
- variant="ghost-black"
1056
- size="sm"
1057
- icon={<Icon name="layout-right-stroked" size={16} />}
1058
- iconOnly
1059
- onClick={handleExpandFromAvatarOnly}
1060
- aria-label="展开会话列表"
1061
- />
1062
- </Tooltip>
1053
+ <Button
1054
+ type="button"
1055
+ variant="ghost-black"
1056
+ size="sm"
1057
+ icon={<Icon name="layout-right-stroked" size={16} />}
1058
+ iconOnly
1059
+ tooltip="展开会话列表"
1060
+ onClick={handleExpandFromAvatarOnly}
1061
+ aria-label="展开会话列表"
1062
+ />
1063
1063
  </div>
1064
1064
  <div className={AVATAR_ONLY_LIST}>
1065
1065
  {avatarOnlyItems.map((item) => (
@@ -1077,65 +1077,60 @@ function ConversationListDefaultVariant({
1077
1077
  <header className={HEADER}>
1078
1078
  <div className={HEADER_MAIN}>
1079
1079
  {!isCardVariant && collapsible ? (
1080
- <Tooltip content="收起会话列表">
1081
- <Button
1082
- type="button"
1083
- variant="ghost-black"
1084
- size="sm"
1085
- icon={<Icon name="layout-right-stroked" size={16} />}
1086
- iconOnly
1087
- onClick={handleCollapseToAvatarOnly}
1088
- aria-label="收起会话列表"
1089
- />
1090
- </Tooltip>
1080
+ <Button
1081
+ type="button"
1082
+ variant="ghost-black"
1083
+ size="sm"
1084
+ icon={<Icon name="layout-right-stroked" size={16} />}
1085
+ iconOnly
1086
+ tooltip="收起会话列表"
1087
+ onClick={handleCollapseToAvatarOnly}
1088
+ aria-label="收起会话列表"
1089
+ />
1091
1090
  ) : null}
1092
1091
  <h2 className={TITLE}>{title}</h2>
1093
1092
  </div>
1094
1093
  {showActions ? (
1095
1094
  <div className={ACTIONS}>
1096
1095
  {showLayoutToggle ? (
1097
- <Tooltip content={isCardVariant ? '切换为默认列表' : '切换为卡片列表'}>
1098
- <Button
1099
- type="button"
1100
- variant="ghost-black"
1101
- size="sm"
1102
- icon={<Icon name="switch-horizontal-01-stroked" size={16} />}
1103
- iconOnly
1104
- onClick={handleVariantToggle}
1105
- aria-label={isCardVariant ? '切换为默认列表' : '切换为卡片列表'}
1106
- />
1107
- </Tooltip>
1108
- ) : null}
1109
- <Tooltip content="搜索会话">
1110
1096
  <Button
1111
1097
  type="button"
1112
1098
  variant="ghost-black"
1113
1099
  size="sm"
1114
- icon={<Icon name="search-lg-stroked" size={16} />}
1100
+ icon={<Icon name="switch-horizontal-01-stroked" size={16} />}
1115
1101
  iconOnly
1116
- aria-label="搜索会话"
1102
+ tooltip={isCardVariant ? '切换为默认列表' : '切换为卡片列表'}
1103
+ onClick={handleVariantToggle}
1104
+ aria-label={isCardVariant ? '切换为默认列表' : '切换为卡片列表'}
1117
1105
  />
1118
- </Tooltip>
1119
- <Tooltip content="筛选会话">
1120
- <Button
1121
- type="button"
1122
- variant="ghost-black"
1123
- size="sm"
1124
- icon={<Icon name="filter-funnel-01-stroked" size={16} />}
1125
- iconOnly
1126
- aria-label="筛选会话"
1127
- />
1128
- </Tooltip>
1129
- <Tooltip content="查看工单文件">
1130
- <Button
1131
- type="button"
1132
- variant="ghost-black"
1133
- size="sm"
1134
- icon={<Icon name="file-05-stroked" size={16} />}
1135
- iconOnly
1136
- aria-label="查看工单文件"
1137
- />
1138
- </Tooltip>
1106
+ ) : null}
1107
+ <Button
1108
+ type="button"
1109
+ variant="ghost-black"
1110
+ size="sm"
1111
+ icon={<Icon name="search-lg-stroked" size={16} />}
1112
+ iconOnly
1113
+ tooltip="搜索会话"
1114
+ aria-label="搜索会话"
1115
+ />
1116
+ <Button
1117
+ type="button"
1118
+ variant="ghost-black"
1119
+ size="sm"
1120
+ icon={<Icon name="filter-funnel-01-stroked" size={16} />}
1121
+ iconOnly
1122
+ tooltip="筛选会话"
1123
+ aria-label="筛选会话"
1124
+ />
1125
+ <Button
1126
+ type="button"
1127
+ variant="ghost-black"
1128
+ size="sm"
1129
+ icon={<Icon name="file-05-stroked" size={16} />}
1130
+ iconOnly
1131
+ tooltip="查看工单文件"
1132
+ aria-label="查看工单文件"
1133
+ />
1139
1134
  </div>
1140
1135
  ) : null}
1141
1136
  </header>
@@ -75,6 +75,7 @@ export default function FullScreenPage({
75
75
  icon={<ArrowLeft size={16} strokeWidth={2} />}
76
76
  iconOnly
77
77
  onClick={onBack}
78
+ tooltip="返回"
78
79
  aria-label="返回"
79
80
  className="shrink-0"
80
81
  data-tfds-component="FullScreenPage.Back"
@@ -3,6 +3,8 @@
3
3
  *
4
4
  * 用于客服工作台 / 在线 Agent / 工单详情右侧信息区,将多个可切换的信息面板
5
5
  * 组织为“主栏 + 拆分栏”结构。组件只负责框架、拆分/合并和 Tabs,不规定 tabs 内容。
6
+ * tabs 来自业务数据动态配置:1 个分类单栏展示,2 个分类最多双栏,3 个及以上分类且宽度足够时最多三栏。
7
+ * 嵌入客服工作台框架时,必须保留整体信息栏拖拽、内部栏间拖拽、拆分/合并和最小宽度保护。
6
8
  */
7
9
 
8
10
  import { useEffect, useMemo, useRef, useState } from 'react';
@@ -10,7 +12,6 @@ import Button from './Button';
10
12
  import FormTitle from './FormTitle';
11
13
  import Icon from './Icon';
12
14
  import Tabs from './Tabs';
13
- import Tooltip from './Tooltip';
14
15
 
15
16
  const DEFAULT_PANELS = [
16
17
  {
@@ -307,20 +308,17 @@ function PanelHeader({
307
308
 
308
309
  {!useTitleFallback ? (
309
310
  <div className={ACTIONS}>
310
- <Tooltip content={splitActionTooltip} placement="top">
311
- <span className="inline-flex">
312
- <Button
313
- type="button"
314
- variant="ghost-black"
315
- size="md"
316
- icon={<Icon name={splitActionIcon} size={16} />}
317
- iconOnly
318
- disabled={splitActionDisabled}
319
- aria-label={splitActionLabel}
320
- onClick={onSplitAction}
321
- />
322
- </span>
323
- </Tooltip>
311
+ <Button
312
+ type="button"
313
+ variant="ghost-black"
314
+ size="md"
315
+ icon={<Icon name={splitActionIcon} size={16} />}
316
+ iconOnly
317
+ tooltip={splitActionTooltip}
318
+ disabled={splitActionDisabled}
319
+ aria-label={splitActionLabel}
320
+ onClick={onSplitAction}
321
+ />
324
322
  </div>
325
323
  ) : null}
326
324
  <span className={HEADER_LINE} aria-hidden="true" />
@@ -17,6 +17,7 @@ export const INFO_DISPLAY_PANEL_TOKEN_MAP = {
17
17
  分栏: [
18
18
  { label: '布局模型', cssProp: 'layout', value: '主栏 + 拆分栏;主栏永远在最右侧,拆分栏固定追加在主栏左侧' },
19
19
  { label: '最大栏数', cssProp: 'max-columns', value: '3 栏(1 个主栏 + 最多 2 个拆分栏)' },
20
+ { label: '动态拆分能力', cssProp: 'split-model', value: '按业务 tabs 动态支持 tab1 / tab2 / tab3:1 个分类单栏展示,2 个分类最多双栏,3 个及以上分类且宽度足够时最多三栏' },
20
21
  { label: 'Tab数量上限', cssProp: 'max-columns', value: '可见栏数同时受 tab 总数限制:1 个 tab 不拆分,2 个 tab 最多 2 栏,>= 3 个 tab 才允许 3 栏' },
21
22
  { label: '单栏最小宽度', cssProp: 'minPanelWidth', value: '200px' },
22
23
  { label: '双栏门槛', cssProp: 'container-width', value: '>= 401px(200px * 2 + 1px 分隔线)' },
@@ -24,6 +25,7 @@ export const INFO_DISPLAY_PANEL_TOKEN_MAP = {
24
25
  { label: '栏间分隔线', cssProp: 'border-left', token: 'border-default', value: '1px' },
25
26
  { label: '空间不足策略', cssProp: 'split-limit', value: '容器宽度 < 401px 时只允许单栏;401px-601px 允许双栏;>= 602px 才允许三栏。宽度不足时主栏拆分按钮进入 Button disabled 态' },
26
27
  { label: '拖拽边界', cssProp: 'resize-handle', value: '组件整体宽度由左外边界单独拖拽;组件内部仅栏与栏之间的分隔线支持拖拽调宽' },
28
+ { label: '客服工作台保留能力', cssProp: 'customer-service-rule', value: '嵌入客服工作台框架时,必须完整保留整体信息栏拖拽、内部栏间拖拽、拆分/合并、宽度门槛和最小栏宽保护' },
27
29
  { label: '拖拽最小宽度', cssProp: 'min-column-width', value: '每栏最小 200px' },
28
30
  { label: '预览验证', cssProp: 'preview-resize', value: '预览态默认撑满容器,并支持从组件左侧边缘拖拽整体宽度,用于验证单栏 / 双栏 / 三栏门槛' },
29
31
  ],
@@ -122,6 +122,7 @@ export default function Modal({
122
122
  icon={<X size={16} strokeWidth={2} />}
123
123
  iconOnly
124
124
  onClick={onClose}
125
+ tooltip="关闭"
125
126
  aria-label="关闭"
126
127
  className="shrink-0"
127
128
  />
@@ -94,6 +94,7 @@ export default function Sheet({
94
94
  icon={<X size={16} strokeWidth={2} />}
95
95
  iconOnly
96
96
  onClick={onClose}
97
+ tooltip="关闭"
97
98
  aria-label="关闭"
98
99
  className="shrink-0"
99
100
  />
@@ -607,6 +607,7 @@ function renderTextButtonValue(value) {
607
607
  iconOnly
608
608
  icon={<Icon name={iconName} size="sm" />}
609
609
  className={[ICON_ONLY_BUTTON_RESET, TEXT_BUTTON_ICON_CLASS, 'ml-[2px]'].join(' ')}
610
+ tooltip={typeof value === 'object' ? value.tooltip || value.ariaLabel || value.label || '单元格操作' : '单元格操作'}
610
611
  aria-label="单元格操作"
611
612
  onClick={onClick}
612
613
  />
@@ -720,6 +721,7 @@ function renderActionsValue(value) {
720
721
  icon={<Icon name={action.iconName || 'dots-horizontal-stroked'} size="sm" />}
721
722
  className={[ICON_ONLY_BUTTON_RESET, MORE_ACTION_BUTTON_CLASS].join(' ')}
722
723
  onClick={action.onClick}
724
+ tooltip={action.tooltip || action.ariaLabel || action.label || '更多操作'}
723
725
  aria-label={action.ariaLabel || action.label || '更多操作'}
724
726
  />
725
727
  );
@@ -755,6 +757,7 @@ function renderDragHandleValue(value) {
755
757
  iconOnly
756
758
  icon={<Icon name="dots-grid-stroked" size="sm" />}
757
759
  className={[ICON_ONLY_BUTTON_RESET, DRAG_HANDLE_BUTTON_CLASS].join(' ')}
760
+ tooltip="拖拽排序"
758
761
  aria-label="拖拽排序"
759
762
  onClick={onClick}
760
763
  />
@@ -888,6 +891,7 @@ function CardFormActions({ record, context, onView, onMore }) {
888
891
  variant="ghost-black"
889
892
  iconOnly
890
893
  icon={<Icon name="dots-horizontal-stroked" />}
894
+ tooltip="更多"
891
895
  aria-label="更多"
892
896
  onClick={() => onMore?.(record, context)}
893
897
  />
@@ -956,6 +960,7 @@ function CardFormItem({
956
960
  variant="ghost-black"
957
961
  iconOnly
958
962
  icon={<Icon name="chevron-right-stroked" />}
963
+ tooltip={expanded ? '收起子项' : '展开子项'}
959
964
  aria-label={expanded ? '收起子项' : '展开子项'}
960
965
  aria-expanded={hasChildren ? expanded : undefined}
961
966
  onClick={(event) => {
@@ -1169,6 +1174,7 @@ export default function Table({
1169
1174
  className={[ICON_ONLY_BUTTON_RESET, PAGE_ARROW_BUTTON_CLASS].join(' ')}
1170
1175
  onClick={() => handlePageChange(safeCurrent - 1)}
1171
1176
  disabled={safeCurrent <= 1}
1177
+ tooltip="上一页"
1172
1178
  aria-label="上一页"
1173
1179
  data-tfds-component="Table.Pagination"
1174
1180
  />
@@ -1207,6 +1213,7 @@ export default function Table({
1207
1213
  className={[ICON_ONLY_BUTTON_RESET, PAGE_ARROW_BUTTON_CLASS].join(' ')}
1208
1214
  onClick={() => handlePageChange(safeCurrent + 1)}
1209
1215
  disabled={safeCurrent >= totalPages}
1216
+ tooltip="下一页"
1210
1217
  aria-label="下一页"
1211
1218
  data-tfds-component="Table.Pagination"
1212
1219
  />
@@ -566,6 +566,7 @@ function BusinessSwitcher({
566
566
  iconOnly
567
567
  icon={<Icon name="switch-horizontal-01-stroked" size="xs" className="text-foreground-muted" aria-hidden="true" />}
568
568
  className="shrink-0"
569
+ tooltip="切换业务线"
569
570
  aria-label="切换业务线"
570
571
  aria-haspopup="menu"
571
572
  aria-expanded={menuOpen}
@@ -1097,6 +1098,7 @@ export default function TagBar({
1097
1098
  radius="rounded"
1098
1099
  iconOnly
1099
1100
  icon={<Icon name="layout-left-stroked" size="sm" aria-hidden="true" />}
1101
+ tooltip={isCollapsed ? '展开标签栏' : '收起标签栏'}
1100
1102
  aria-label={isCollapsed ? '展开标签栏' : '收起标签栏'}
1101
1103
  onClick={() => updateCollapsed(!isCollapsed)}
1102
1104
  data-tfds-component="TagBar.CollapseToggle"
@@ -111,6 +111,7 @@ export default function Toast({
111
111
  icon={<X size={16} strokeWidth={2} />}
112
112
  iconOnly
113
113
  onClick={onClose}
114
+ tooltip="关闭"
114
115
  aria-label="关闭"
115
116
  className="shrink-0 border-transparent bg-transparent shadow-none"
116
117
  />
@@ -413,6 +413,7 @@ export const Upload = ({
413
413
  radius="rounded"
414
414
  iconOnly
415
415
  icon={<Icon name="refresh-cw-01-stroked" size="xs" />}
416
+ tooltip="重试上传"
416
417
  onClick={(e) => {
417
418
  e.stopPropagation();
418
419
  retryItem(fileItem);
@@ -1065,7 +1065,7 @@ export const COMPONENTS = [
1065
1065
  name: 'Card',
1066
1066
  element: 'article',
1067
1067
  category: 'business',
1068
- description: '业务信息摘要卡片,支持数据卡片、商品卡片、信息卡片1和信息卡片2。颜色使用遵循背景反衬原则:灰色背景用白底卡,白色背景用灰底卡。',
1068
+ description: '业务信息摘要卡片,支持数据卡片、商品卡片、信息卡片1和信息卡片2。所有卡片分类统一遵循背景反衬原则:灰色/浅灰/非纯白背景用白底卡,纯白背景用灰底卡。',
1069
1069
  componentFile: './components/Card.jsx',
1070
1070
  tokensFile: './components/Card.tokens.js',
1071
1071
  props: [
@@ -1137,9 +1137,9 @@ export const COMPONENTS = [
1137
1137
  '【信息卡片2视觉】info2 不包含柔光、流动光带或特殊 hover 位移;背景颜色、边框颜色和 hover 投影完全复用普通 Card 的 color=white / grey 规则。animatedTone 只影响左上图标容器色系,不影响卡片背景、边框和底部标签。',
1138
1138
  '【信息卡片2图标 Hover】卡片 hover 时,左上图标容器必须自动切换为当前 animatedTone 对应的 hover 背景、纯白 icon、透明描边:brand→brand-500、blue→blue-500、purple→purple-500、green→green-500、orange→orange-500、grey→black。禁止 hover 后继续显示浅底、深色 icon 或描边色。',
1139
1139
  '【颜色】color=white 使用 65% 白底 + 白色描边;color=grey 使用 Blue Grey 100 背景 + Blue Grey 300 描边',
1140
- '【容器映射(强约束)】Card 的颜色必须与所处背景做反衬:当 Card 所在父容器是“灰底页面/灰底区块”(如 blueGrey-200 页面底、白卡内的浅灰分区)→ Card 用 `color="white"`;当父容器本身是“白底卡片/白底面板”→ Card 用 `color="grey"`,用灰底卡片做二级区块分层,避免白底叠白底',
1140
+ '【容器映射(强约束)】Card 的颜色必须与所处背景做反衬,而且这条规则对所有 Card 分类统一生效:当 Card 所在父容器是“灰底页面/灰底区块/浅灰分区/其他非纯白背景”→ Card 用 `color="white"`;当父容器本身是“纯白底卡片/纯白底面板”→ Card 用 `color="grey"`,用灰底卡片做二级区块分层,避免白底叠白底',
1141
1141
  '【配置面板颜色】所有 Card 分类(数据卡片、商品卡片、信息卡片1、信息卡片2)都必须优先展示“颜色”配置项,并提供白底 / 灰底两种选择。平台预览中切换右上角画布底色时,卡片颜色必须按反衬规则自动同步:灰底画布 → 白底卡片,白底画布 → 灰底卡片。',
1142
- '【一句话记忆】如果卡片背景环境是灰色,就选“白底”分类卡片;如果卡片背景环境是纯白色,就选“灰底”分类卡片。不要让 Card 和父级背景同色相贴。',
1142
+ '【一句话记忆】如果卡片背景环境是灰色、浅灰色或其他非纯白色,就选“白底”分类卡片;如果卡片背景环境是纯白色,就选“灰底”分类卡片。不要让 Card 和父级背景同色相贴。',
1143
1143
  '【状态】白底卡默认态使用 65% 白底 + 轻白描边;hover 后补满白底并出现业务卡片专用投影;灰底卡保持灰底与灰描边并保留投影反馈',
1144
1144
  '【指标】指标项推荐控制在 3 项以内,图标 14px、文字 12px,项间距 20px',
1145
1145
  '【标签】通用 tags 只用于数据卡片,展示在左下角并与右侧操作按钮水平对齐;商品卡片和信息卡片1禁止渲染通用 tags,主标题左侧不得出现“标签”等前缀标签,运行时会隐藏误注入的 white Tag。Card 内所有标签默认必须使用圆角矩形样式 `radius="md"`,不使用全圆胶囊 `radius="full"`;数据卡片、商品状态标签、信息卡片1徽标都遵守该规则。数据卡片不论是否展示图标、也不论 dataIconStyle 为 tone 或 inverse,默认标签都必须统一使用 grey 标签样式。商品状态标签和信息卡片1徽标仅可展示在右上角并与标题水平对齐。信息卡片1徽标与 infoIconStyle 联动:`tone` 时一律 grey,`inverse` 时默认彩色 purple;卡片彩色标签优先使用 purple(紫色)/ teal(青绿色)/ blue(蓝色)/ cyan(青色)/ orange(橙色),避免优先使用 pink、red、yellow 等过强警示或装饰色。商品状态标签复用 green + l + md;不显示图标和关闭按钮,建议使用 2-4 字短标签。',
@@ -1302,7 +1302,7 @@ export const COMPONENTS = [
1302
1302
  name: 'InfoDisplayPanel',
1303
1303
  element: 'section',
1304
1304
  category: 'business',
1305
- description: '信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并可按当前选中 tab 拆分出独立栏,最多支持 3 栏。',
1305
+ description: '信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并按业务动态 tab1 / tab2 / tab3 拆分为 1-3 栏,支持整体宽度与栏间宽度拖拽。',
1306
1306
  componentFile: './components/InfoDisplayPanel.jsx',
1307
1307
  tokensFile: './components/InfoDisplayPanel.tokens.js',
1308
1308
  props: [
@@ -1344,6 +1344,7 @@ export const COMPONENTS = [
1344
1344
  '【不适用场景】整页客服工作台外框用 CustomerServiceWorkspaceFrame;左侧会话 / 工单队列用 ConversationList;单个业务摘要用 Card;字段型详情或可批量操作数据用 Form / Table。',
1345
1345
  '【结构】外层为一个白色圆角容器,默认使用 `w-full` 撑满父容器可用宽度;内部按“拆分栏数组 + 主栏”组织。每一栏都必须包含 56px 顶部 Tabs 栏和内容区;顶部横线只保留 Header 内 1 条通栏下描边,左右贴满当前栏容器。Tabs 交互、文字、选中态与 3px Brand 指示线必须复用基础 `Tabs variant="line" size="lg"`,禁止手写 tab button 或手写选中线。栏与栏之间用 1px 垂直分隔线,禁止通过 gap 制造分栏空隙。',
1346
1346
  '【分栏能力】默认单栏时,全部 tabs 位于主栏;主栏永远在最右侧,拆分出的独立栏固定追加在主栏左侧。每栏右上角始终只有 1 个动作按钮,拆分与合并统一使用 `Button variant="ghost-black" size="md" iconOnly tooltip="拆分/合并"` 显示提示文案。最多支持 3 栏,即 1 个主栏 + 最多 2 个拆分栏。',
1347
+ '【动态 tab1/tab2/tab3 拆分】InfoDisplayPanel 必须按业务实际 tabs 动态决定可见栏数,而不是写死三栏:只有 1 个分类时自动降级为单栏标题 + 内容;有 2 个分类时最多支持 2 栏;有 3 个及以上分类且容器宽度达到三栏门槛时才支持 3 栏。tab 的数量、名称、顺序、内容都必须来自 `panels[].tabs` / `panels[].content` 或 `renderPanelContent`,不要把“托管助手 / 历史工单 / 工单日志”等示例固定写死到所有业务页面。',
1347
1348
  '【Tabs来源】Tabs 的数量、名称和顺序必须跟随具体生成页面的业务信息架构动态生成,由 `panels[].tabs` 提供;不要把默认示例 tabs 当作真实业务规则,也不要为了凑满 3 栏强行生成无意义的 tab。',
1348
1349
  '【Tab数量与拆分上限】可拆栏数同时受容器宽度和 tab 总数限制:仅 1 个 tab 时不使用 Tabs,运行时自动降级为 `FormTitle variant="card"` 标题展示且不显示拆分按钮;仅 2 个 tab 时最多拆分为 2 栏;大于等于 3 个 tab 且容器宽度达到 602px 时才允许拆分为 3 栏。',
1349
1350
  '【拆分规则】点击主栏右上角“拆分”时,只拆出当前选中的主栏 tab;拆分后该 tab 从主栏 tabs 中移除,并以独立栏形式出现在主栏左侧。主栏保留剩余未拆分 tabs,顺序始终保持传入时的原始顺序。',
@@ -1360,6 +1361,7 @@ export const COMPONENTS = [
1360
1361
  '【组合关系】InfoDisplayPanel 可作为 CustomerServiceWorkspaceFrame 的 mainPanel 内容;放入主白卡时宽高使用 `size-full` 或父容器约束,外层不要再套一层大白卡。',
1361
1362
  '【嵌入客服工作台时的语义】在客服工作台框架模版里,InfoDisplayPanel 不是独立静态侧栏,而是“当前左侧选中会话 / 工单”的辅助信息区。左侧会话列表默认选中项变化时,InfoDisplayPanel 内各 tab 的内容也必须同步切换到该当前对象对应的数据上下文,例如用户信息、历史工单、工单日志、视频信息、沟通记录都应属于同一个当前处理对象。',
1362
1363
  '【联动边界】客服工作台里的右侧白卡应被视为一个整体工作区:左侧 IM 对话区负责当前线程,右侧 InfoDisplayPanel 负责同一线程的辅助信息。两者由外层模板统一接收当前对象 id 并同步刷新;InfoDisplayPanel 不应自己脱离左侧上下文单独维持另一份“当前对象”。',
1364
+ '【嵌入客服工作台时的能力保留】当 InfoDisplayPanel 被客服工作台框架使用时,必须保留整体信息栏宽度拖拽、内部栏间拖拽、动态 tab 拆分 / 合并、单 tab 降级、宽度门槛和最小栏宽保护。AI 生成页面不得把 InfoDisplayPanel 替换成静态 Card 列、普通 Tabs 面板或固定宽 div,也不得删除拖拽热区和拆分按钮。',
1363
1365
  '【可控状态】业务需要同步 URL / 埋点 / 右侧主区状态时,使用 `activeTabs + onTabChange` 控制每栏当前 tab,并使用 `onSplitChange` 感知当前拆分出来的 tab 列表与实际栏数;`columnCount` / `defaultColumnCount` 仅保留为兼容字段,不再作为主交互入口推荐,默认最大栏数按组件逻辑自动支持到 3 栏。',
1364
1366
  ],
1365
1367
  examples: [
@@ -22,8 +22,9 @@ import ChatMessage, {
22
22
  * - 800px 居中对话流
23
23
  *
24
24
  * 状态:
25
- * - phase = 'chat' 默认进入,展示端到端 mock 对话(覆盖全部 ChatMessage 子组件)
26
- * - phase = 'welcome' 点「新会话」清空回到欢迎屏(Hero + 居中 ChatInput + 推荐 prompt)
25
+ * - 空对话 / 新建会话态 当消息为空时,必须展示新建会话页面(Hero + 居中 ChatInput + 推荐 prompt)
26
+ * - phase = 'chat' 有消息时展示消息流
27
+ * - phase = 'welcome' → 点「新会话」后进入空对话 / 新建会话态
27
28
  *
28
29
  * 关键词路由(输入框发送文本时):
29
30
  * - 含「整理 / 分析 / 生成 / 梳理」 → 走完整任务规划链路:
@@ -325,9 +326,15 @@ function messageToChatProps(msg, isLatest, handlers = {}) {
325
326
 
326
327
  export default function ChatConversationPattern({
327
328
  title = '抖音电商客服售后政策梳理',
329
+ initialMessages,
328
330
  }) {
329
- const [phase, setPhase] = useState('chat'); // 'chat' | 'welcome'
330
- const [messages, setMessages] = useState(buildInitMessages);
331
+ const resolvedInitialMessagesRef = useRef(
332
+ Array.isArray(initialMessages) ? initialMessages : buildInitMessages(),
333
+ );
334
+ const [messages, setMessages] = useState(() => resolvedInitialMessagesRef.current);
335
+ const [phase, setPhase] = useState(() => (
336
+ resolvedInitialMessagesRef.current.length === 0 ? 'welcome' : 'chat'
337
+ )); // 'chat' | 'welcome'
331
338
  /* ── ChatInput 受控状态机 ──
332
339
  * inputView:'default' | 'replying' | 'busy'
333
340
  * · default → 静止 / 失焦 / AI 完成回复(含 replying & busy 完成)
@@ -361,7 +368,7 @@ export default function ChatConversationPattern({
361
368
  /* 自动滚到底:用 ResizeObserver 监听消息容器内容高度变化
362
369
  * 覆盖所有场景:新消息追加、流式 step 逐步推出、ChatMessage 内部折叠展开 */
363
370
  useEffect(() => {
364
- if (phase !== 'chat') return undefined;
371
+ if (phase !== 'chat' || messages.length === 0) return undefined;
365
372
  const el = scrollRef.current;
366
373
  if (!el) return undefined;
367
374
  const inner = el.firstElementChild;
@@ -371,12 +378,14 @@ export default function ChatConversationPattern({
371
378
  });
372
379
  ro.observe(inner);
373
380
  return () => ro.disconnect();
374
- }, [phase]);
381
+ }, [phase, messages.length]);
375
382
 
376
383
  /* 重置回欢迎状态 */
377
384
  const handleNewSession = useCallback(() => {
378
385
  setMessages([]);
379
386
  setPhase('welcome');
387
+ setPrefillText('');
388
+ setPrefillSeed((s) => s + 1);
380
389
  }, []);
381
390
 
382
391
  /* ── 取消链路:用户在任务规划卡里点「取消」 ──
@@ -610,9 +619,10 @@ export default function ChatConversationPattern({
610
619
  }, [handleNewSession]);
611
620
 
612
621
  const lastIdx = messages.length - 1;
622
+ const isNewConversationState = phase === 'welcome' || messages.length === 0;
613
623
 
614
- /* 当前阶段标题:欢迎屏标题简化为「新会话」 */
615
- const displayTitle = phase === 'welcome' ? '新会话' : title;
624
+ /* 当前阶段标题:空对话 / 新建会话态统一显示「新会话」 */
625
+ const displayTitle = isNewConversationState ? '新会话' : title;
616
626
 
617
627
  return (
618
628
  <div
@@ -625,9 +635,18 @@ export default function ChatConversationPattern({
625
635
  border: '1px solid var(--color-border-default, #E4E7EC)',
626
636
  }}
627
637
  >
628
- <TopBar title={displayTitle} onNewSession={handleNewSessionFull} disableNewSession={phase === 'welcome'} />
638
+ <TopBar title={displayTitle} onNewSession={handleNewSessionFull} disableNewSession={isNewConversationState} />
629
639
 
630
- {phase === 'chat' ? (
640
+ {isNewConversationState ? (
641
+ <NewConversationPhase
642
+ onSend={handleSend}
643
+ onStop={handleStop}
644
+ onPrefill={prefillInput}
645
+ inputView={inputView}
646
+ prefillText={prefillText}
647
+ prefillSeed={prefillSeed}
648
+ />
649
+ ) : (
631
650
  <ChatPhase
632
651
  scrollRef={scrollRef}
633
652
  messages={messages}
@@ -643,15 +662,6 @@ export default function ChatConversationPattern({
643
662
  prefillText={prefillText}
644
663
  prefillSeed={prefillSeed}
645
664
  />
646
- ) : (
647
- <WelcomePhase
648
- onSend={handleSend}
649
- onStop={handleStop}
650
- onPrefill={prefillInput}
651
- inputView={inputView}
652
- prefillText={prefillText}
653
- prefillSeed={prefillSeed}
654
- />
655
665
  )}
656
666
  </div>
657
667
  );
@@ -755,10 +765,10 @@ function ChatPhase({ scrollRef, messages, lastIdx, handlers, onSend, onStop, inp
755
765
  }
756
766
 
757
767
  /* ============================================================
758
- * WelcomePhase欢迎阶段:CATCAT 头像 + OLA AI 标题 + 欢迎语 + 推荐 chip
759
- * 复用 CopilotPagePattern 的欢迎态视觉,ChatInput 仍底部吸底
768
+ * NewConversationPhase — AI 对话页的唯一空会话 / 新建会话页面
769
+ * messages 为空时,必须展示该页面;复用 Copilot welcome 视觉,ChatInput 仍底部吸底
760
770
  * ============================================================ */
761
- function WelcomePhase({ onSend, onStop, onPrefill, inputView, prefillText, prefillSeed }) {
771
+ function NewConversationPhase({ onSend, onStop, onPrefill, inputView, prefillText, prefillSeed }) {
762
772
  return (
763
773
  <>
764
774
  {/* 中部 hero:自适应剩余高度,居中展示头像/标题/欢迎语/chips */}
@@ -936,12 +946,14 @@ function TopBar({ title, onNewSession, disableNewSession }) {
936
946
  icon={<Icon name="message-plus-square-stroked" />}
937
947
  onClick={onNewSession}
938
948
  disabled={disableNewSession}
949
+ tooltip="新建会话"
939
950
  aria-label="新建会话"
940
951
  />
941
952
  <Button
942
953
  variant="ghost-black"
943
954
  iconOnly
944
955
  icon={<Icon name="clock-rewind-stroked" />}
956
+ tooltip="历史记录"
945
957
  aria-label="历史记录"
946
958
  />
947
959
  </div>
@@ -262,7 +262,7 @@ export default function ChatHomePagePattern() {
262
262
  </div>
263
263
  </div>
264
264
 
265
- {/* ③ 卡片网格(随页面整体滚动,左右 40px) */}
265
+ {/* ③ 卡片网格(当前直接坐浅灰页面底,所以统一使用白底 Card 做内容承载) */}
266
266
  <div
267
267
  className="shrink-0"
268
268
  style={{ padding: '0 40px 32px' }}
@@ -91,7 +91,7 @@ function TopBar({ copilotOpen, onToggleCopilot }) {
91
91
  function TopBarLead({ copilotOpen, onToggleCopilot }) {
92
92
  return (
93
93
  <>
94
- <Button variant="outline-black" iconOnly icon={<Icon name="arrow-left-stroked" />} aria-label="返回" />
94
+ <Button variant="outline-black" iconOnly icon={<Icon name="arrow-left-stroked" />} tooltip="返回" aria-label="返回" />
95
95
 
96
96
  <span
97
97
  className="font-semibold text-base leading-[22px] whitespace-nowrap"
@@ -158,7 +158,7 @@ function TopBarActions() {
158
158
  </div>
159
159
 
160
160
  <div className="flex items-center gap-2">
161
- <Button variant="ghost-black" iconOnly icon={<Icon name="dots-horizontal-stroked" />} aria-label="更多" />
161
+ <Button variant="ghost-black" iconOnly icon={<Icon name="dots-horizontal-stroked" />} tooltip="更多" aria-label="更多" />
162
162
  <Button variant="outline-black">次操作</Button>
163
163
  <Button variant="primary" icon={<Icon name="plus-stroked" />}>主操作</Button>
164
164
  </div>
@@ -191,9 +191,9 @@ function CopilotPanel({ onClose }) {
191
191
  </span>
192
192
  </div>
193
193
  <div className="flex items-center gap-0.5 shrink-0">
194
- <Button variant="ghost-black" iconOnly icon={<Icon name="message-plus-square-stroked" />} aria-label="新建会话" />
195
- <Button variant="ghost-black" iconOnly icon={<Icon name="clock-stroked" />} aria-label="历史记录" />
196
- <Button variant="ghost-black" iconOnly icon={<Icon name="layout-left-stroked" />} aria-label="收起面板" onClick={onClose} />
194
+ <Button variant="ghost-black" iconOnly icon={<Icon name="message-plus-square-stroked" />} tooltip="新建会话" aria-label="新建会话" />
195
+ <Button variant="ghost-black" iconOnly icon={<Icon name="clock-stroked" />} tooltip="历史记录" aria-label="历史记录" />
196
+ <Button variant="ghost-black" iconOnly icon={<Icon name="layout-left-stroked" />} tooltip="收起面板" aria-label="收起面板" onClick={onClose} />
197
197
  </div>
198
198
  </div>
199
199
 
@@ -362,7 +362,7 @@ function ContentCard() {
362
362
 
363
363
  <Button variant="ghost-black">流程单测</Button>
364
364
  <Button variant="ghost-black">批量测试</Button>
365
- <Button variant="ghost-black" iconOnly icon={<Icon name="git-branch-02-stroked" />} aria-label="对比" />
365
+ <Button variant="ghost-black" iconOnly icon={<Icon name="git-branch-02-stroked" />} tooltip="对比" aria-label="对比" />
366
366
  </div>
367
367
  </div>
368
368
 
@@ -453,19 +453,18 @@ function IMThreadHeader({ thread }) {
453
453
 
454
454
  function ThreadActionButton({ action, ariaExpanded, onClick, ref }) {
455
455
  return (
456
- <Tooltip content={action.label} placement="top" triggerClassName="inline-flex shrink-0">
457
- <Button
458
- ref={ref}
459
- type="button"
460
- variant="ghost-black"
461
- size="md"
462
- iconOnly
463
- aria-label={action.label}
464
- aria-expanded={ariaExpanded}
465
- onClick={onClick}
466
- icon={<Icon name={action.iconName} size="sm" aria-hidden="true" />}
467
- />
468
- </Tooltip>
456
+ <Button
457
+ ref={ref}
458
+ type="button"
459
+ variant="ghost-black"
460
+ size="md"
461
+ iconOnly
462
+ tooltip={action.label}
463
+ aria-label={action.label}
464
+ aria-expanded={ariaExpanded}
465
+ onClick={onClick}
466
+ icon={<Icon name={action.iconName} size="sm" aria-hidden="true" />}
467
+ />
469
468
  );
470
469
  }
471
470
 
@@ -279,6 +279,7 @@ export default function TabTopBarListPage({ onCreateQA }) {
279
279
  variant="ghost-black"
280
280
  iconOnly
281
281
  icon={<Icon name="dots-horizontal-stroked" />}
282
+ tooltip="更多操作"
282
283
  aria-label="更多操作"
283
284
  />
284
285
  <Button variant="outline-black">知识测试</Button>
@@ -468,6 +469,7 @@ function DetailPanel({ card, typeLabel, open }) {
468
469
  variant="ghost-black"
469
470
  iconOnly
470
471
  icon={<Icon name="dots-horizontal-stroked" />}
472
+ tooltip="更多操作"
471
473
  aria-label="更多操作"
472
474
  />
473
475
  <Button
@@ -47,6 +47,7 @@ export const PATTERNS = [
47
47
  '【顶部工具】指标区右侧按钮仅用于平台框架级入口,例如全局设置、平台公告、平台数据统计等;最多展示 3 个按钮,超出时应按平台级优先级裁切;按钮必须复用 `Button iconOnly` 并提供 `tooltip` / `aria-label`,不要放置页面内局部操作或业务表单操作。',
48
48
  '【工作区层级】Workspace 外层只负责横向布局、覆盖层级与拖拽,不承担可见圆角;左侧板块自身为 `bg-white/50 + border-white + rounded-xl`,右侧主面板自身为白色 `surface + border-white + rounded-xl`,两者高度一致、宽度不同,并通过主面板 `margin-left: -32px` 形成“右侧白板覆盖在左侧灰板上”的叠压视觉,而不是中间留缝的并排关系。',
49
49
  '【可见圆角】最终可见的 6 个角统一为 16px:左侧板块左上 / 左下两个角露出;右侧主白卡四角露出;左侧板块右上 / 右下两个角必须被右侧主白卡完整覆盖。主面板覆盖量必须大于圆角半径(推荐 32px = 2 × 16px),防止主面板左上 / 左下圆角切角处露出左侧板块的右边界或描边,避免出现“残缺一块”的视觉。',
50
+ '【框架级能力不可删减】当 AI 生成页面使用客服工作台框架时,必须完整保留框架级交互能力:左侧列表 / 卡片容器宽度拖拽、左侧与右侧主白卡之间的覆盖式拖拽、右侧 InfoDisplayPanel 整体宽度拖拽、InfoDisplayPanel 内部栏间拖拽、响应式最小宽度保护、自动折叠、动态分栏和上下文同步。禁止只复刻截图外观却删除拖拽条、键盘微调、宽度下限、自动适配或 tab 拆分逻辑。',
50
51
  '【左右拖拽】左侧区域与右侧主白卡之间必须提供纵向拖拽热区,默认热区 8px;拖拽热区覆盖在主面板左边缘上,不占用布局宽度、不制造中间空隙;拖动时调整左侧区域宽度并让右侧主面板 `flex-1 min-w-0` 自动占满剩余空间;左侧默认宽度 432px(400px 可视会话列表 + 32px 主面板覆盖区);拖拽最小宽度优先由左侧业务组件最小可用宽度决定,例如 ConversationList 默认列表纯头像锁定 88px 时,左侧板块最小宽度为 `max(100px 框架兜底, 88px + 32px 覆盖区) = 120px`;ConversationList 卡片列表最小内容宽度为 333px,左侧板块最小宽度为 365px;右侧纯白主容器最小宽度 380px,左侧最大可拖宽度需按 `工作区宽度 + 32px 覆盖量 - 380px` 动态计算;拖拽条需支持键盘左右方向键微调。',
51
52
  '【左侧插槽】左侧半透明容器默认承载平台业务组件 `ConversationList`,用于会话 / 工单 / 托管队列切换;左侧内容区必须扣除右侧 32px 覆盖安全区,避免被主白卡裁切;`ConversationList` 展开态四周内容间距固定 16px。嵌入时 `ConversationList` 必须设置 `resizable={false}`、保留 `collapsible` 与 `autoCollapseOnNarrow`、`style={{ width: "100%", height: "100%" }}`,并在外层框架接入 `onLayoutWidthRequest` 与 `onVariantChange`:默认列表传 `leftContentMinWidth={88}`,卡片列表切换为 `leftContentMinWidth={333}`。框架统一管理宽度拖拽,但拖拽下限跟随子组件能力;点击会话列表左上角收起按钮时,外层左侧半透明容器必须同步吸附缩窄到 120px,不允许只把 ConversationList 缩到头像列而外层仍保留大片空白。',
52
53
  '【左侧选中项 = 右侧上下文源】客服工作台框架内左侧 `ConversationList` 默认必须存在一个当前选中项;它不是单纯的导航高亮,而是整个右侧主白卡的上下文源。切换左侧会话 / 工单 / 托管项时,右侧纯白容器里的 IM 对话区和 InfoDisplayPanel 信息区都必须同步切换到同一个当前处理对象,禁止出现“左侧已切到 B 会话,但右侧聊天仍是 A、信息区还是 C”的上下文错位。预览实现必须以 `activeConversationId` 作为单一事实源:左侧列表头像、标题、单号与右侧 IM Header 的头像、标题、会话 ID、渠道、沟通时长和消息脚本必须一一对应,不能只更新高亮而复用固定聊天剧本。',
@@ -56,6 +57,8 @@ export const PATTERNS = [
56
57
  '【右侧纯白卡语义】右侧纯白色主卡定义的是“当前处理对象的完整工作区”,不是两个彼此独立的小组件容器。IMConversationPattern 负责当前线程消息往返,InfoDisplayPanel 负责同一对象的辅助信息、历史工单、日志、视频信息、用户信息等;两者必须共享同一个当前会话 / 工单 / 线程上下文。',
57
58
  '【状态同步原则】当业务接真实数据、URL 或埋点时,应以左侧当前选中对象 id 作为单一事实源(single source of truth),再派生右侧聊天流和信息面板内容;不要让 IMConversationPattern 和 InfoDisplayPanel 各自维护独立当前对象状态。',
58
59
  '【右侧信息栏整体拖拽】在客服工作台框架模版里,右侧 InfoDisplayPanel 必须支持整体宽度拖拽,拖拽热区位于信息栏左边界,默认热区 8px;拖拽时改变的是右侧信息栏整体宽度,左侧 IM 聊天区自动占满剩余空间,而不是改由 InfoDisplayPanel 本体处理页面级宽度。推荐默认宽度约 380px,最小宽度 320px,并保证左侧 IM 区至少保留 360px 可读宽度;同时保留 InfoDisplayPanel 内部相邻栏之间的原生拖拽能力。',
60
+ '【InfoDisplayPanel 动态 tab 拆分】右侧信息展示区必须保留 InfoDisplayPanel 的动态 tab1 / tab2 / tab3 栏拆分能力:业务只有 1 个分类时降级为单栏标题 + 内容;业务有 2 个分类时最多支持拆成 2 栏;业务有 3 个及以上分类且宽度达到三栏门槛时支持拆成 3 栏。tab 数量、名称、顺序和内容必须来自当前会话 / 工单上下文的业务数据,不允许固定写死 3 个静态按钮,也不允许用多个 Card 或 div 手搓三栏替代。',
61
+ '【拖拽与拆分保留验收】客服工作台生成页必须同时满足:拖动左侧列表边界时 ConversationList 默认列表 / 卡片列表容器实时适配;拖动右侧 InfoDisplayPanel 左边界时信息栏整体宽度变化且 IM 区自动吃剩余空间;拆出 InfoDisplayPanel tab 后,栏间分隔线可继续调节相邻栏宽,且每栏不小于 200px。任一能力缺失都视为没有正确使用客服工作台框架。',
59
62
  '【右侧插槽】如业务需要替换右侧主内容,可放 ChatConversationPattern、Table、表单详情或工单处理面板;但客服接待类页面优先保持“IM 对话 + InfoDisplayPanel”的组合,不要用多个普通 Card 临时拼出右侧信息区。',
60
63
  '【AI 选型】当 prompt 出现“客服工作台框架 / 客服工作台 / 客服在线工作台 / 在线客服工作台 / 在线 Agent / Agent 工作台 / 基础模式 / 托管模式 / 客服名称在线状态 / 顶部指标工具条”等客服工作台信号时,优先选本页面模板;但本模板右侧主内容仍必须包含 IMConversationPattern 聊天区和 InfoDisplayPanel 信息区。如果只是全局左侧导航,选 NavBar;如果只是会话队列,选 ConversationList;如果只是单条消息,选 ChatMessage / ChatBubble。',
61
64
  '【容器语义】本模板自身已经包含浅灰页面底与右侧主白卡,生成页面时不要外层再套 `Card color="grey"`、`bg-surface rounded-xl` 或大白卡 section,否则会形成“灰底 + 大白卡 + 框架”的错误嵌套。',
@@ -208,7 +211,7 @@ export default function MyPage({ defaultSelectedItemId = 'example-1' }) {
208
211
  '【框架不动】外框灰底 + 左侧 `NavBar` 固定不变;右侧内容区必须 `flex-1 min-w-0 min-h-0 overflow-y-auto overflow-x-hidden`,由右侧内容区作为唯一滚动容器承载整体页面滚动;Hero、ChatInput、筛选行、卡片网格要一起被滑动,禁止只让下方卡片列表单独 `overflow-y-auto`。',
209
212
  '【Hero 区】居中欢迎标题 + 最大宽 680px 的 ChatInput 直接坐浅灰底;Hero 主标题使用 `text-4xl leading-10`;默认使用 `ChatInput variant="default"` 作为入口主输入;标准节奏为 `padding: 84px 40px 80px`,标题区较旧版整体上移 36px。标题区副标题与 ChatInput 之间必须保留 48px 间距(比普通标题说明节奏额外增加 24px),保证输入框不贴近标题区。AI 渐变只用于输入框内部 / AI 标识,不作为整块 Hero 白底。',
210
213
  '【筛选与分类】分类切换必须用 `Tabs size="sm"`;筛选行 `padding: 16px 40px`,左 Tabs、右搜索。模板/助手搜索框是辅助筛选入口,不是页面主输入,必须固定宽度并自动对齐到页面右侧:可用外层 `<div className="ml-auto shrink-0" style={{ flex: "0 0 240px", width: "240px", minWidth: "240px", maxWidth: "240px" }}>` 包裹 `Input`,也可直接给 `Input` 传 `style={{ flex: "0 0 240px", "--size-input-width": "240px" }}`;`Input` 的 `style` 作用于根容器。Tabs 外层使用 `flex shrink-0` 保持左侧胶囊 Tabs 可见。筛选行容器禁止使用 `flex-wrap`、`justify-between`、`space-y-*`、`flex-col`;禁止让筛选搜索框 `flex-1`、`w-full`、撑满 Tabs 右侧剩余空间,或换到 Tabs 下方单独占一行。',
211
- '【卡片列表】使用自适应 grid(优先 `repeat(auto-fit, minmax(min(320px, 100%), 1fr))`),网格 `gap-4`,禁止固定列宽导致窄屏横向溢出;卡片使用 `Card color="white"`;列表区域作为普通内容块跟随右侧页面整体滚动,使用 `shrink-0` + `padding: 0 40px 40px`,禁止在卡片列表自身设置 `overflow-y-auto` 造成只有卡片区滚动。',
214
+ '【卡片列表】使用自适应 grid(优先 `repeat(auto-fit, minmax(min(320px, 100%), 1fr))`),网格 `gap-4`,禁止固定列宽导致窄屏横向溢出;当前列表直接坐浅灰页面底,所以卡片统一使用 `Card color="white"`。通用铁律是:纯白容器里必须改用 `color="grey"`,灰色/浅灰/其他非纯白容器里必须使用 `color="white"`,且这条规则对所有 Card 分类都生效;列表区域作为普通内容块跟随右侧页面整体滚动,使用 `shrink-0` + `padding: 0 40px 40px`,禁止在卡片列表自身设置 `overflow-y-auto` 造成只有卡片区滚动。',
212
215
  '【内容可扩展】Tab 项数量和卡片数据替换为业务真实数据;卡片 stats/tags 按业务维度自定义',
213
216
  ],
214
217
  code: `import NavBar from './components/NavBar';
@@ -284,7 +287,7 @@ export default function ChatHomePage() {
284
287
  </div>
285
288
  </div>
286
289
 
287
- {/* 卡片列表:随右侧页面整体滚动,卡片自身是白卡,外层不包白卡 */}
290
+ {/* 卡片列表:当前直接坐浅灰页面底,所以卡片统一使用白底 Card;若未来改成纯白容器,必须同步切换为 grey 灰底卡 */}
288
291
  <div className="shrink-0" style={{ padding: '0 40px 40px' }}>
289
292
  <div className="grid gap-4" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(min(320px, 100%), 1fr))' }}>
290
293
  {/* 👉 替换为业务卡片数据 */}
@@ -356,9 +359,10 @@ export default function ChatHomePage() {
356
359
  rules: [
357
360
  '【五模板定位】本模板只用于“AI 任务会话 / AI 助手会话”的持续协作页:用户第一步是继续追问、确认规划、查看流式执行、查看 AI 产物卡或 follow-up。若用户还没开始任务,用 `ChatHomePagePattern`;若右侧主产物/主编辑区才是页面主角,AI 只是侧助,用 `CopilotPagePattern`;若是真人客服/私信/站内信回复,用 `IMConversationPattern`;若是管理一批会话/任务列表,用 `BasePageFramePattern`。',
358
361
  '【布局 Recipe】必须按 `LAYOUT_RECIPES.md` 选择 `chat-conversation`:消息流和底部输入是主工作流,滚动归属消息流;底部 `ChatInput` 固定吸底,不随消息滚动。',
359
- '【组件选型门禁】所有消息必须用 `ChatMessage`:用户消息用 `role="user"`,AI 消息用 `header` 并按需要组合 `thinking` / `plan` / `taskGroups` / `resultText` / `resultArtifacts` / `followUps`;底部输入必须用 `ChatInput`;顶部和消息操作用 `Button` + `Icon`;欢迎/空会话态可用 `Empty` 或模板内欢迎屏。禁止用 `ChatBubble`、Card 或裸 div 模拟 AI 消息。',
362
+ '【组件选型门禁】所有消息必须用 `ChatMessage`:用户消息用 `role="user"`,AI 消息用 `header` 并按需要组合 `thinking` / `plan` / `taskGroups` / `resultText` / `resultArtifacts` / `followUps`;底部输入必须用 `ChatInput`;顶部和消息操作用 `Button` + `Icon`;空会话必须使用模板内置的「新建会话页」,不要退回 `Empty`、空白消息区或自制欢迎卡。禁止用 `ChatBubble`、Card 或裸 div 模拟 AI 消息。',
360
363
  '【整体框架】外框灰底(color-blueGrey-200);不带左侧 NavBar,整页就是一个独立会话工作区;不嵌中间白卡,顶导栏 / 消息区 / ChatInput 三层都直接坐灰底上;业务页面根容器不额外加外圈圆角或描边',
361
364
  '【顶导栏】shrink-0,左侧 ghost-black 返回箭头 + 会话标题(truncate),右侧按权重递增:share-07 / 会话详情 outline-black / 更多 dots / 新会话 primary(主操作唯一固定最右)',
365
+ '【顶导栏图标按钮】顶导栏内所有仅图标操作必须使用 `Button iconOnly tooltip="操作文案"`,hover/focus 时展示 Tooltip,且 tooltip 为字符串时由 Button 自动补 `aria-label`;禁止只传 `aria-label` 或只放 Icon 让用户猜操作。',
362
366
  '【布局结构】flex h-full flex-col:① 顶导栏 shrink-0 ② 中部消息区 flex-1 + min-h-0 + overflow-y-auto 独立内滚 ③ ChatInput shrink-0 吸底;不要给中部加白卡背景',
363
367
  '【宽度规格】对话流和 ChatInput 都用 800px + mx-auto 居中;对话流相对 ChatInput 左右各缩进 12px(消息区 padding "0 20px",ChatInput padding "0 8px"),让消息看起来比输入框窄一圈',
364
368
  '【消息组件统一】所有消息(用户 / AI)一律用 ChatMessage 渲染。用户消息 role="user" + userContent / userAttachments;AI 消息 header=true + 任意组合 thinking / leadText / plan / taskGroups / resultText / resultArtifacts / followUps。禁止用 ChatBubble 渲染 AI 回复',
@@ -369,7 +373,7 @@ export default function ChatHomePage() {
369
373
  '【取消链路】用户点任务规划卡的「取消」:1) 卡片变 confirmed 置灰 2) 追加用户消息「取消」 3) 600ms 后追加 AI 短答寒暄("已取消任务,还有其他问题随时找我~")',
370
374
  '【追问交互】followUps 字符串数组在父组件 wrap 成 { items, onSelect: onSend } 形式,点击 chip 必须等价于立即发送该文本:先追加一条右侧 user 气泡,再由 AI 追加对应的新答复 / 产物 / 下一组 followUps;不要只回填输入框等待用户二次发送',
371
375
  '【自动滚底】用 ResizeObserver 监听消息列表 firstElementChild 的高度变化,任何高度增长都 scrollTo({ top: scrollHeight, behavior: smooth });覆盖新消息追加 / 流式 step / 折叠展开 三种触发场景,比单独 useEffect([messages]) 更稳',
372
- '【欢迎屏】点「新会话」→ phase 切到 welcome:清空 messages,中部居中展示 CATCAT 头像(66px 渐变描边圆 + 蓝色投影 + 32px catcat.svg)+「OLA AI」标题 + 欢迎语 + 3 条推荐 chip(白底 0.6 透明 + inset 1px 白边 + 右箭头);ChatInput 仍 shrink-0 吸底(位置和 chat 阶段保持一致),placeholder 改成"需要我为你做什么…";用户在欢迎屏发送或点 chip → phase 自动切回 chat 走完整链路',
376
+ '【新建会话页 = 空对话唯一标准态】只要当前会话没有消息(初次进入空会话、外部传入 `messages=[]`、或用户点击「新会话」清空消息),都必须展示统一的新建会话页:中部居中展示 CATCAT 头像(66px 渐变描边圆 + 蓝色投影 + 32px catcat.svg)+「OLA AI」标题 + 欢迎语 + 3 条推荐 chip(白底 0.6 透明 + inset 1px 白边 + 右箭头);底部 `ChatInput` 仍 shrink-0 吸底,placeholder 固定为"需要我为你做什么…";禁止出现空白消息区、默认 mock 对话残留或与图示不一致的空状态。',
373
377
  '【关键词路由】① 含「整理 / 分析 / 生成 / 梳理 / 输出 / 汇总」→ 任务规划卡链路(待用户点开始/取消) ② 含「停止 / 取消」→ AI 短答「已停止当前任务,需要时再叫我。」 ③ 其他 → AI 短答从备选句池随机一句,避免机械化',
374
378
  '【任务规划卡 defaultConfirmed】历史消息(mock 中的已处理卡片)通过 plan.defaultConfirmed=true 让卡片初始就处于禁用置灰态;TaskPlanCard 内部用 useState(plan.defaultConfirmed === true) 初始化 confirmed,并用 useEffect 同步 props 变化',
375
379
  ],
@@ -1241,10 +1241,10 @@ export const PREVIEW_REGISTRY = {
1241
1241
  }
1242
1242
 
1243
1243
  if (color === 'grey') {
1244
- lines.push('/* 白色背景容器里,推荐使用灰底 Card 做层次分隔 */');
1244
+ lines.push('/* 所有 Card 分类都遵守同一规则:白色背景容器里,推荐使用灰底 Card 做层次分隔 */');
1245
1245
  lines.push('<div className="bg-white p-6">');
1246
1246
  } else {
1247
- lines.push('/* 灰色背景容器里,推荐使用白底 Card 做内容承载 */');
1247
+ lines.push('/* 所有 Card 分类都遵守同一规则:灰色、浅灰或其他非纯白背景容器里,推荐使用白底 Card 做内容承载 */');
1248
1248
  lines.push('<div className="bg-blueGrey-200 p-6">');
1249
1249
  }
1250
1250
  lines.push(' <Card');
package/src/index.d.ts CHANGED
@@ -155,7 +155,7 @@ export interface TagBarProps extends TfdsCommonProps {
155
155
  }
156
156
  export const TagBar: React.FC<TagBarProps>;
157
157
 
158
- /** Card — 业务信息摘要卡片,支持数据卡片、商品卡片、信息卡片1和信息卡片2。颜色使用遵循背景反衬原则:灰色背景用白底卡,白色背景用灰底卡。 */
158
+ /** Card — 业务信息摘要卡片,支持数据卡片、商品卡片、信息卡片1和信息卡片2。所有卡片分类统一遵循背景反衬原则:灰色/浅灰/非纯白背景用白底卡,纯白背景用灰底卡。 */
159
159
  export interface CardProps extends TfdsCommonProps {
160
160
  /** enum<data | product | info | info2 | animated>, default: "data" */
161
161
  type?: "data" | "product" | "info" | "info2" | "animated";
@@ -261,7 +261,7 @@ export interface ConversationListProps extends TfdsCommonProps {
261
261
  }
262
262
  export const ConversationList: React.FC<ConversationListProps>;
263
263
 
264
- /** InfoDisplayPanel — 信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并可按当前选中 tab 拆分出独立栏,最多支持 3 栏。 */
264
+ /** InfoDisplayPanel — 信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并按业务动态 tab1 / tab2 / tab3 拆分为 1-3 栏,支持整体宽度与栏间宽度拖拽。 */
265
265
  export interface InfoDisplayPanelProps extends TfdsCommonProps {
266
266
  /** array, default: null */
267
267
  panels?: unknown[];