@tfdesign/b-end 1.1.2 → 1.1.4

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.1.2",
3
+ "version": "1.1.4",
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,6 +1,6 @@
1
1
  ---
2
2
  name: tfds
3
- description: "体服B端设计系统 v1.1.2,支持一键调用TFDS组件生成符合体服设计规范的UI组件与页面。"
3
+ description: "体服B端设计系统 v1.1.4,支持一键调用TFDS组件生成符合体服设计规范的UI组件与页面。"
4
4
  disable-model-invocation: true
5
5
  metadata:
6
6
  version: "3"
@@ -102,7 +102,7 @@ metadata:
102
102
  1. **栈对齐**:项目为 Vite + React + Tailwind v4;全局样式已 `@import "tailwindcss"` 且已 `@import "@tfdesign/b-end/theme.css"`;Tailwind 已扫描 `node_modules/@tfdesign/b-end/**/*.{js,jsx}`。
103
103
  2. **先选页面骨架**:按 `PAGE_ARCHETYPES.md` 判断页面范式 → 按 `LAYOUT_RECIPES.md` 选唯一主 recipe → 用 `components.summary.json` 初选 `kind: "page-pattern"` / `kind: "page-template"` → 回查 `components.index.json`:
104
104
  - **结构化对象管理** → `BasePageFramePattern`(浅灰底整页 + 多白卡分区,贴浏览器边缘,无外圈圆角/描边)
105
- - **从 0 发起 AI 任务** → `ChatHomePagePattern`(右侧主内容区**保持灰底**,⛔ 禁止包大白卡 wrapper;模板卡用 `<Card color="white" />`)
105
+ - **从 0 发起 AI 任务** → `ChatHomePagePattern`(右侧主内容区**保持灰底**,Hero 标题 + `ChatInput` 必须居中;⛔ 禁止包大白卡 wrapper;⛔ 禁止用 `Form` / `labelAi` 包裹或模拟 Hero 主输入;模板卡用 `<Card color="white" />`)
106
106
  - **围绕 AI 任务持续协作** → `ChatConversationPattern`(消息流必须用 `ChatMessage`)
107
107
  - **对象编辑 / 产物查看 + AI 侧助** → `CopilotPagePattern`
108
108
  - **真实 IM / 客服 / 私信线程** → `IMConversationPattern`(`ChatBubble` + `ChatInput variant="im-basic"`)
@@ -121,6 +121,7 @@ metadata:
121
121
  - `card` → Card 头、摘要条
122
122
  - 同页必须拉开层级;描述默认隐藏(无说明诉求不写 `description`);标题状态 Tag 用 `titleSuffix`,⛔ 不进右侧 action 区
123
123
  - **页面级 header 直接坐灰底**,⛔ 不得包白卡(详见 LAYOUT_RULES § 1.5 / [CHECKLIST.md §12](./CHECKLIST.md))
124
+ - **ChatHome 例外边界**:AI 入口页 Hero 区的“AI / 助手名 / 问候语 + ChatInput”不是表单字段标题,也不是白卡标题;⛔ 禁止写 `<Form items={[{ label: "AI", type: "input" | "textarea" }]} />`、`labelAi` 或 Form 字段标题来实现输入框顶部的 AI 标识。正确做法是按 `ChatHomePagePattern`:Hero 容器 `items-center text-center`,标题/副标题居中,`<ChatInput variant="default" />` 放在 `maxWidth: 680px` 的居中容器内。
124
125
  7. **状态徽标 / 图标 / 链接**:状态分类 → `<Tag size="m" />`(⛔ `<span>` 假徽标);图标 → `<Icon name="kebab-case" size="sm" />`(⛔ `lucide-react` 单独引入);文字链 → `<Button variant="text-brand" />`(⛔ 裸 `<a>`)。
125
126
  8. **整页高度与滚动 / 多栏 / IM 骨架**(详见 `LAYOUT_RULES.md`):
126
127
  - 根容器默认 `h-dvh + overflow-hidden + flex flex-col + w-full min-w-0`
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "system": "b-end",
3
3
  "skill": "tfds",
4
- "generatedAt": "2026-05-14T17:33:55.296Z",
4
+ "generatedAt": "2026-05-16T18:40:07.215Z",
5
5
  "summary": "B 端 COMPONENTS 37 条 + 列表页模板 4 + PATTERNS 6;import 一律来自 @tfdesign/b-end。",
6
6
  "components": [
7
7
  {
@@ -1566,7 +1566,7 @@
1566
1566
  "kind": "component",
1567
1567
  "name": "InfoDisplayPanel",
1568
1568
  "category": "business",
1569
- "description": "信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并按业务动态 tab1 / tab2 / tab3 拆分为 1-3 栏,支持整体宽度与栏间宽度拖拽。",
1569
+ "description": "信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并按业务动态 tab1 / tab2 / tab3 拆分为 1-3 栏,支持整体宽度与栏间宽度拖拽,且每个 tab 内容都可由业务完全自定义。",
1570
1570
  "import": {
1571
1571
  "from": "@tfdesign/b-end",
1572
1572
  "default": "InfoDisplayPanel"
@@ -1635,7 +1635,7 @@
1635
1635
  ],
1636
1636
  "rules": [
1637
1637
  "【字重实现规范】涉及 semibold / bold 的运行时字重时,不要直接依赖 `font-semibold`、`font-bold` 等 Tailwind 语义类;统一改用显式 `font-weight` token 写法:600 → `[font-weight:var(--font-semibold)]`,700 → `[font-weight:var(--font-bold)]`(需提高优先级时对整条任意属性加 Tailwind important 前缀)。原因:Tailwind v4 下以 `--font-` 开头的 token 名可能被误解析到 font-family 语义,导致字重不稳定。分档与典型落点见 Skill `GLOBAL_DESIGN_RULES.md` §2.2:`FormTitle`/表单字段标题/会话列表项标题/Button/Tabs 选中等主路径为 600;700 仅组件 rules 已约定处。",
1638
- "【组件定位】InfoDisplayPanel 是“信息展示面板框架”,只负责外框、顶部 Tabs、拆分/合并、分栏调宽、内容插槽和基础间距;它不提供业务内容默认占位,不替代 Card、Table、Form、ConversationList 或客服工作台整页框架。",
1638
+ "【组件定位】InfoDisplayPanel 是“信息展示面板框架”,只负责外框、顶部 Tabs、拆分/合并、分栏调宽、内容插槽和基础间距;它不提供业务内容默认占位,不替代 Card、Table、Form、ConversationList 或客服工作台整页框架。每个 tab 的正文区域是开放插槽,可承载任意业务自定义内容。",
1639
1639
  "【触发引用】当页面需要在同一工作区右侧并列查看“托管助手 / 历史工单 / 工单日志 / 信息工具 / 视频信息 / 用户信息 / 沟通记录”等多个详情面板,并且用户需要把当前关注的 tab 临时拆出来并排对照时,优先使用 InfoDisplayPanel;如果只是单块内容展示或普通 tabs 切换,不应使用该业务框架。",
1640
1640
  "【客服业务优先规则】只要页面触及客服业务、客服工作台、客服接待、工单处理、售后处理、订单/商品上下文核对、客服资料查看等场景,并需要展示客服工单信息、商品信息、订单信息、用户信息、客服相关信息或沟通记录等辅助信息板块,默认优先使用 InfoDisplayPanel 承载这些信息区;禁止用多个 Card、div 白卡或自制 tabs 临时拼出客服信息侧栏。",
1641
1641
  "【客服分类 Tabs】客服信息区涉及多个分类时,必须通过 InfoDisplayPanel 顶部 Tabs 切换分类,常见分类包括“工单信息 / 商品信息 / 订单信息 / 用户信息 / 客服信息 / 沟通记录 / 处理日志 / 风险提示”。分类名称、顺序和内容必须来自当前业务上下文;不要把这些示例硬编码成所有页面的固定 tab。",
@@ -1651,12 +1651,14 @@
1651
1651
  "【分栏门槛】每栏最小宽度固定 `200px`,双栏最小需求空间为 `401px`,三栏最小需求空间为 `602px`;小于 `401px` 时只能单栏,`401px-601px` 时最多双栏,`>= 602px` 时最多三栏。",
1652
1652
  "【拖拽调宽】组件整体宽度必须通过左外边界那 1 条手柄统一调整;当拆分为 2 栏或 3 栏后,组件内部只保留栏与栏之间的分隔线拖拽,用于调整左右相邻两栏宽度。禁止在组件最左侧再叠加第二条“第一栏宽度”手柄。所有栏位最小宽度统一为 `200px`,最大宽度不单独限制,只受外层容器可用宽度约束。",
1653
1653
  "【预览验收】组件预览默认撑满预览容器,并提供组件左侧边缘的整体宽度拖拽;单栏、双栏和三栏切换都应通过同一预览宽度拖拽链路验证,不再依赖固定宽度画板。",
1654
- "【默认内容】组件默认不渲染任何骨架屏、灰色占位卡片或示意内容;InfoDisplayPanel 只提供框架、Tabs、拆分/合并、拖拽调宽和内容插槽。业务内容必须由 `panels[].content` 或 `renderPanelContent` 注入。",
1654
+ "【默认内容】组件默认不渲染任何骨架屏、灰色占位卡片或示意内容;InfoDisplayPanel 只提供框架、Tabs、拆分/合并、拖拽调宽和内容插槽。业务内容必须由 `panels[].content` 或 `renderPanelContent({ panel, index, activeTabId })` 注入。",
1655
1655
  "【内容内距】每栏内容区四周内边距固定 16px,内容块之间纵向间距固定 16px;内容区默认 `flex-col` 排列,子元素默认 `min-width: 0; width: 100%`,需要跟随当前 tab 栏宽度自动伸缩适配。每栏内容区独立滚动,内容多时只滚动当前栏,不撑高外层面板。AI 生成内容时应优先使用响应式宽度、自动换行和内部滚动,避免 `w-[固定值]`、绝对定位和固定大宽度表格。",
1656
1656
  "【Tabs规范】每栏顶部使用页签语义组织,统一使用基础 `Tabs variant=\"line\" size=\"lg\"`;Header 横向内边距为 16px,Tabs 文案之间保持基础 Tabs 的 item padding 与间距,不额外手写按钮间距。右侧动作按钮与 Tabs 区域保持 12px 间距。选中态为基础 Tabs 的 semibold + Brand 500 3px 指示线;未选中态为基础 Tabs 的 normal 字重和次级色。主栏展示未拆分 tabs 集合,拆分栏只展示各自承载的单个 tab。Tabs 只切换本栏内容,不改变页面路由。",
1657
1657
  "【单Tab降级】当业务只有 1 个分类时,容器不应为了形式感渲染 Tabs;运行时会用 `FormTitle variant=\"card\"` 展示该分类标题,内容区仍按 16px 内距承载业务组件。AI 生成页面时,如果只有单个分类,应优先直接使用标题 + 内容结构,除非后续明确会扩展为多 tab。",
1658
- "【内容承载】每一个 tab 栏都可以承载任意业务组件元素,包括信息卡片、表单、表格片段、日志列表、图片/视频信息、实验命中详情或自定义复合组件;被承载组件必须优先使用 `w-full min-w-0` 等自适应写法,避免固定宽度导致拆分栏变窄时溢出。",
1659
- "【AI生成约束】AI 生成具体业务页面时,应把真实信息卡片、表单、工单日志或内容详情放进 `panels[].content` `renderPanelContent`;不要手写外层分栏容器、不要用多个 Card + gap 临时拼出三栏框架,也不要依赖 InfoDisplayPanel 的默认占位内容。",
1658
+ "【内容承载】每一个 tab 栏都可以承载任意业务组件元素,包括 ChatMessage 托管助手、信息卡片、表单、表格片段、日志列表、图片/视频信息、实验命中详情、风险提示、订单/商品详情、售后策略、处理工具或自定义复合组件;被承载组件必须优先使用 `w-full min-w-0`、自动换行和内部滚动等自适应写法,避免固定宽度导致拆分栏变窄时溢出。",
1659
+ "【自定义接入方式】简单场景优先把内容直接放在 `panels[].content`:例如 `{ id: \"order\", tabs: [{ id: \"order\", label: \"订单信息\" }], content: <OrderInfo data={currentOrder} /> }`。复杂场景使用 `renderPanelContent` `panel.id` / `activeTabId` 动态返回内容,适合多个 tab 共享同一个当前会话上下文、按 tab 懒渲染,或把内容生成逻辑集中管理。",
1660
+ "【客服工作台自定义内容】嵌入客服工作台时,InfoDisplayPanel 的 tab 内容必须由外层当前 `activeConversationId` / `currentTicketId` 派生后注入,确保托管助手、用户画像、历史工单、工单日志、商品/订单信息、沟通记录等内容和左侧 IM 聊天区指向同一处理对象;不要让信息面板内部自行维护另一套当前对象状态。",
1661
+ "【AI生成约束】AI 生成具体业务页面时,应把真实信息卡片、表单、工单日志、ChatMessage 托管助手或内容详情放进 `panels[].content` 或 `renderPanelContent`;不要手写外层分栏容器、不要用多个 Card + gap 临时拼出三栏框架,也不要依赖 InfoDisplayPanel 的默认占位内容。",
1660
1662
  "【组合关系】InfoDisplayPanel 可作为 CustomerServiceWorkspaceFrame 的 mainPanel 内容;放入主白卡时宽高使用 `size-full` 或父容器约束,外层不要再套一层大白卡。",
1661
1663
  "【嵌入客服工作台时的语义】在客服工作台框架模版里,InfoDisplayPanel 不是独立静态侧栏,而是“当前左侧选中会话 / 工单”的辅助信息区。左侧会话列表默认选中项变化时,InfoDisplayPanel 内各 tab 的内容也必须同步切换到该当前对象对应的数据上下文,例如用户信息、历史工单、工单日志、视频信息、沟通记录都应属于同一个当前处理对象。",
1662
1664
  "【联动边界】客服工作台里的右侧白卡应被视为一个整体工作区:左侧 IM 对话区负责当前线程,右侧 InfoDisplayPanel 负责同一线程的辅助信息。两者由外层模板统一接收当前对象 id 并同步刷新;InfoDisplayPanel 不应自己脱离左侧上下文单独维持另一份“当前对象”。",
@@ -1680,6 +1682,14 @@
1680
1682
  "label": "自定义面板",
1681
1683
  "code": "<InfoDisplayPanel panels={[{ id: \"assistant\", tabs: [{ id: \"overview\", label: \"托管助手\" }], content: <AssistantPanel /> }, { id: \"history\", tabs: [{ id: \"tickets\", label: \"历史工单\" }], content: <HistoryTickets /> }]} />"
1682
1684
  },
1685
+ {
1686
+ "label": "客服工作台自定义信息 Tabs",
1687
+ "code": "const infoPanels = [\n {\n id: \"assistant\",\n tabs: [{ id: \"assistant\", label: \"托管助手\" }],\n content: <ChatMessage role=\"ai\" taskGroups={assistantFlow} resultText={recommendedReply} />,\n },\n {\n id: \"order\",\n tabs: [{ id: \"order\", label: \"订单信息\" }, { id: \"risk\", label: \"风险提示\" }],\n content: <OrderInfoPanel conversationId={activeConversationId} />,\n },\n {\n id: \"records\",\n tabs: [{ id: \"history\", label: \"历史工单\" }, { id: \"logs\", label: \"工单日志\" }],\n content: <TicketRecordPanel ticketId={currentTicketId} />,\n },\n];\n\n<InfoDisplayPanel panels={infoPanels} className=\"size-full\" />"
1688
+ },
1689
+ {
1690
+ "label": "动态渲染 Tab 内容",
1691
+ "code": "<InfoDisplayPanel\n panels={[\n { id: \"assistant\", tabs: [{ id: \"assistant\", label: \"托管助手\" }] },\n { id: \"context\", tabs: [{ id: \"user\", label: \"用户信息\" }, { id: \"order\", label: \"订单信息\" }] },\n ]}\n renderPanelContent={({ panel, activeTabId }) => {\n if (panel.id === \"assistant\") return <AssistantPanel conversationId={activeConversationId} />;\n if (activeTabId === \"user\") return <UserProfile userId={currentUserId} />;\n if (activeTabId === \"order\") return <OrderInfo orderId={currentOrderId} />;\n return null;\n }}\n/>"
1692
+ },
1683
1693
  {
1684
1694
  "label": "单分类自动降级为标题",
1685
1695
  "code": "<InfoDisplayPanel panels={[{ id: \"profile\", tabs: [{ id: \"profile\", label: \"用户信息\" }], content: <UserProfile /> }]} />"
@@ -3233,7 +3243,7 @@
3233
3243
  "kind": "component",
3234
3244
  "name": "Form",
3235
3245
  "category": "basic",
3236
- "description": "B端表单组合组件,负责字段标题、说明、错误反馈与 top/left 布局;字段控件复用平台已有基础组件,覆盖 13 类表单字段。",
3246
+ "description": "B端表单组合组件,本质是字段标题 / 说明 / 错误反馈与各类表单基础组件的组合层;字段控件完整复用对应基础组件的默认能力、交互、状态、尺寸和 AI 推荐能力。",
3237
3247
  "import": {
3238
3248
  "from": "@tfdesign/b-end",
3239
3249
  "default": "Form"
@@ -3313,8 +3323,9 @@
3313
3323
  }
3314
3324
  ],
3315
3325
  "rules": [
3316
- "【组件定位】Form 是字段组合层,只管理 label、提示、错误反馈、top/left 布局和字段编排,不重新定义已有表单控件样式",
3317
- "【控件复用】已有基础组件必须复用:input 复用 Input,textarea 复用 TextArea(仅传基组件已有 props,默认 minRows=3、resize 纵向增高与 TextArea 一致;System Prompt / JSON / 代码参数字段必须传 variant=\"code\"),input-number 复用 InputNumber,select 复用 Select,tag-input 复用 TagInput,checkbox 复用 CheckboxGroup,radio 复用 RadioGroup,switch 复用 Switch,date-picker 复用 DatePicker,time-picker 复用 TimePicker,slider 复用 Slider,upload 复用 Upload",
3326
+ "【组件定位】Form 是字段组合层,本质是 `FormTitle` / 字段标题 / 提示 / 错误反馈 / top-left 布局 + 各类型表单基础组件的组合;它不重新定义已有表单控件样式、交互或能力,也不是一套新的输入控件体系。",
3327
+ "【控件复用】已有基础组件必须复用:input 复用 Input,textarea 复用 TextArea(仅传基组件已有 props,默认 minRows=3、resize 纵向增高与 TextArea 一致;System Prompt / JSON / 代码参数字段必须传 variant=\"code\"),input-number 复用 InputNumber,select 复用 Select,tag-input 复用 TagInput,checkbox 复用 CheckboxGroup,radio 复用 RadioGroup,switch 复用 Switch,date-picker 复用 DatePicker,time-picker 复用 TimePicker,slider 复用 Slider,upload 复用 Upload",
3328
+ "【能力继承】Form 中字段类型对应哪个基础组件,就完整继承该基础组件的默认能力、交互、状态、尺寸、token、禁用态、错误态、值受控方式和回调语义;Form 只负责把 label / helpText / errorText / required 等字段外壳组合起来,不允许在 Form 内把同类型控件做成与基础组件不同的行为。",
3318
3329
  "【布局】Form 在页面主内容区、白色卡片容器与纵向堆叠场景中默认 `w-full min-w-0 self-stretch`;labelPosition=\"top\" 时字段容器与控件区都默认全宽,label 与控件间距 4px;labelPosition=\"left\" 时标签列固定 96px、间距 24px、右侧控件区使用 `flex-1 min-w-0 w-full` 撑满剩余宽度",
3319
3330
  "【正文表单宽度铁律】页面主表单、白卡/卡片内配置表单、澄清确认卡片、抽屉/弹窗正文表单、侧栏详情表单中,Input / Select / TextArea / InputNumber / DatePicker / TimePicker / TagInput / Slider / InputGroup 这类字段型控件必须默认撑满字段内容列,且同一表单区域左右边界对齐、宽度一致。多个字段上下排列在同一卡片容器内时,每个字段都应随卡片内容区动态撑满,不受 240px / 200px / 320px 的筛选搜索宽度限制。",
3320
3331
  "【筛选工具条宽度例外】只有列表筛选栏、工具条、紧凑查询区、AI 首页模板搜索、Tabs 右侧搜索这类横向辅助查询入口,才允许固定中窄宽:搜索 Input 默认约 240px、最小 200px、最大 320px;Select / DatePicker / TimePicker / TagInput 等筛选控件常用 160-240px(日期时间范围可更宽)。禁止把筛选工具条固定宽套到业务表单正文。",
@@ -3322,12 +3333,18 @@
3322
3333
  "【紧凑/特殊控件宽度】Switch、普通 CheckboxGroup、普通 RadioGroup 的控件本体按语义保持自然宽度,但字段容器仍占满表单区域并与其它字段左边界对齐;卡片型 Radio/Checkbox 选项可按场景设置 `w-full` 形成等宽选项。Upload 字段区域占满表单区域,内部图片 tile 保持固定尺寸并自动换行,不强行拉伸 tile。",
3323
3334
  "【自定义控件宽度】使用 children 或 item.control 传入自定义字段时,外层必须补 `className=\"w-full min-w-0 max-w-full\"`;否则会绕过 Form 内置的 `!w-full` 控件宽度约束,导致 Input、Select、TextArea 等宽度不一致",
3324
3335
  "【详情页全集预览】组件详情页的“全集”分类按字段类型从上到下单列排列,每个组件间距 40px,超出预览画布时在画布内滚动查看",
3325
- "【字段配置】items 数组每项支持 id、label、type、required、labelPosition、middleHelpText、helpText、error、errorText、disabled、value/defaultValue/onChange 等字段;当字段命中 AI 推荐语义时,可继续透传 aiSuggestion、aiSuggestions、onAdoptSuggestion、onRefreshAiSuggestions Input / Select / TextArea",
3336
+ "【字段配置】items 数组每项支持 id、label、type、required、labelPosition、middleHelpText、helpText、error、errorText、disabled、value/defaultValue/onChange 等字段;字段项上未被 Form 消费的基础组件 props 应继续透传给对应控件,保证 Form 内控件能力与单独使用基础组件时一致。",
3337
+ "【AI推荐能力继承】如果某个字段类型对应的基础组件支持 AI 推荐,那么 Form 中同类型字段必须拥有完全一致的 AI 推荐能力:type=\"input\" 继承 Input 的 aiSuggestion / aiSuggestions / onAdoptSuggestion / onRefreshAiSuggestions;type=\"textarea\" 继承 TextArea 的同名能力;type=\"select\" 在默认 Select 模式下继承 Select 的 AI 推荐能力。Form 不单独创造新的 AI 推荐 UI,也不改变推荐区位置、间距、hover、刷新、采纳、写入值和回调行为。",
3338
+ "【AI推荐标题标签】当 Form 的某个字段启用 AI 推荐能力时,该字段标题右侧默认必须展示标准“AI”标签,等价于自动开启 labelAi;AI 标签用于提示该字段正在由智能推荐辅助填写,不需要业务额外再手动配置标题-AI。若字段未启用 AI 推荐,则 labelAi 仍可按普通标题附加样式独立配置。",
3339
+ "【AI推荐标题标签边界】Form 的 `labelAi` 只适用于 Form 内部字段标题,不能外溢为页面 Hero 标题、ChatInput 顶部身份提示或 AI 入口页标题。AI 入口页、AI 对话页、ChatInput Hero 区、消息输入区顶部的“AI / 助手名 / 问候语”都不得用 Form 或 Form 字段 label 实现,应遵守对应页面模板和 ChatInput 规范。",
3340
+ "【AI推荐配置预览】组件详情页左侧配置区必须在字段类型支持 AI 推荐时展示“AI推荐”显隐配置项:当字段类型为 Input / TextArea / Select(默认模式)时可切换显示或隐藏推荐区,并实时预览对应基础组件的 AI 推荐交互;当画布预览区显示 AI 推荐时,下方“标题-AI”开关必须自动打开并处于选中态;切换到不支持 AI 推荐的字段类型或“全集”预览时,该配置项必须隐藏且不向字段透传 AI 推荐 props。",
3341
+ "【AI推荐透传边界】不支持 AI 推荐的基础组件(如 InputNumber、TagInput、CheckboxGroup、RadioGroup、Switch、DatePicker、TimePicker、Slider、Upload 等)在 Form 中也不应额外展示 AI 推荐区;Select 的 tag 模式仍遵循 Select 自身规则,不支持 AI 推荐。AI 生成 Form 时,只有需求明确出现“AI推荐 / 智能推荐 / 建议填写 / 推荐文案 / 推荐选项”等语义,才给对应字段透传 AI 推荐 props。",
3342
+ "【不适用场景】Form 不用于承载 AI 入口页 Hero 主输入、AI 对话页底部输入、IM/客服消息输入、ChatInput 顶部猫条或输入框上方身份提示;这些场景必须直接使用 `ChatInput` 或对应页面模板。若只是为了显示“AI”标签或标题而使用 Form,属于错误选型。",
3326
3343
  "【标题附加样式】单字段标题支持 labelOptional、labelRequired、labelAi、labelHelp 四个布尔开关,可与纯文字标题组合显示;渲染顺序固定为 可选 -> 必填 -> AI -> Help",
3327
3344
  "【标题字重】字段标题、必填星号与可选文案统一使用 `semibold` token,即 `[font-weight:var(--font-semibold)]`;不要继续使用 `font-bold` 或手写 700 字重",
3328
3345
  "【AI 标签】当 labelAi=true 时,必须复用标准 `<Tag variant=\"ai\" radius=\"full\" fontWeight=\"bold\" size=\"m\">AI</Tag>`;禁止在 Form 内手写渐变胶囊、手写圆角块或用普通文字 span 模拟 AI 标签",
3329
3346
  "【标题必填】labelRequired 用于标题星号装饰;若未单独配置,仍可通过 required + requiredMark 复用现有星号逻辑",
3330
- "【配置联动】组件详情页切换字段类型时,左侧配置区需动态追加当前被引用基础组件自身的枚举配置,并透传到对应字段控件(textarea 与 TextArea 详情配置侧栏一致:内容、超长计数、状态、默认高度、状态、高度适配)",
3347
+ "【配置联动】组件详情页切换字段类型时,左侧配置区需动态追加当前被引用基础组件自身的枚举配置,并透传到对应字段控件(textarea 与 TextArea 详情配置侧栏一致:内容、超长计数、状态、默认高度、状态、高度适配);AI 推荐显隐也必须按字段类型动态出现/隐藏。",
3331
3348
  "【TagInput 字段】Form 中 type=\"tag-input\" 的标签颜色固定为灰色、Tag 尺寸固定为 M,不暴露颜色或尺寸配置",
3332
3349
  "【反馈优先级】error=true 时展示红色图标 + 错误文案,并将可复用控件 status 设为 error;无错误时展示 helpText",
3333
3350
  "【未独立组件】InputGroup 尚未作为独立基础组件存在,Form 内部仅做轻量组合展示;如后续沉淀为基础组件,应替换为独立组件引用",
@@ -3355,6 +3372,18 @@
3355
3372
  "label": "选择器字段",
3356
3373
  "code": "<Form items={[{ id: \"city\", label: \"城市\", type: \"select\", options: [{ value: \"hz\", label: \"杭州\" }] }]} />"
3357
3374
  },
3375
+ {
3376
+ "label": "AI推荐 · Input字段",
3377
+ "code": "<Form items={[{ id: \"reply\", label: \"推荐回复\", type: \"input\", aiSuggestions: [\"您好,正在为您核实,请稍候。\", \"抱歉给您带来不便,这边已经帮您加急处理。\"], onAdoptSuggestion: (suggestion) => console.log(suggestion), onRefreshAiSuggestions: () => {} }]} />"
3378
+ },
3379
+ {
3380
+ "label": "AI推荐 · TextArea字段",
3381
+ "code": "<Form items={[{ id: \"remark\", label: \"处理备注\", type: \"textarea\", aiSuggestions: [\"建议优先整理用户诉求、处理时效和需补充的材料信息。\", \"如果需要后续交接,可一并记录当前判责结论和预计回访时间。\"], onAdoptSuggestion: (suggestion) => console.log(suggestion), onRefreshAiSuggestions: () => {} }]} />"
3382
+ },
3383
+ {
3384
+ "label": "AI推荐 · Select字段",
3385
+ "code": "<Form items={[{ id: \"category\", label: \"问题分类\", type: \"select\", options: [{ value: \"refund\", label: \"退款问题\" }, { value: \"invoice\", label: \"发票问题\" }, { value: \"account\", label: \"账号问题\" }], aiSuggestions: [\"退款问题\", \"账号问题\"], onAdoptSuggestion: (value, option) => console.log(value, option), onRefreshAiSuggestions: () => {} }]} />"
3386
+ },
3358
3387
  {
3359
3388
  "label": "全量示例",
3360
3389
  "code": "<Form className=\"!gap-10\" />"
@@ -7235,6 +7264,8 @@
7235
7264
  "【状态同步原则】当业务接真实数据、URL 或埋点时,应以左侧当前选中对象 id 作为单一事实源(single source of truth),再派生右侧聊天流和信息面板内容;不要让 IMConversationPattern 和 InfoDisplayPanel 各自维护独立当前对象状态。",
7236
7265
  "【右侧信息栏整体拖拽】在客服工作台框架模版里,右侧 InfoDisplayPanel 必须支持整体宽度拖拽,拖拽热区位于信息栏左边界,默认热区 8px;拖拽时改变的是右侧信息栏整体宽度,左侧 IM 聊天区自动占满剩余空间,而不是改由 InfoDisplayPanel 本体处理页面级宽度。推荐默认宽度约 380px,最小宽度 320px,并保证左侧 IM 区至少保留 360px 可读宽度;同时保留 InfoDisplayPanel 内部相邻栏之间的原生拖拽能力。",
7237
7266
  "【InfoDisplayPanel 动态 tab 拆分】右侧信息展示区必须保留 InfoDisplayPanel 的动态 tab1 / tab2 / tab3 栏拆分能力:业务只有 1 个分类时降级为单栏标题 + 内容;业务有 2 个分类时最多支持拆成 2 栏;业务有 3 个及以上分类且宽度达到三栏门槛时支持拆成 3 栏。tab 数量、名称、顺序和内容必须来自当前会话 / 工单上下文的业务数据,不允许固定写死 3 个静态按钮,也不允许用多个 Card 或 div 手搓三栏替代。",
7267
+ "【右侧信息 Tab 区内容定义】客服工作台右侧 InfoDisplayPanel 是“当前处理对象的辅助信息工作区”,每个 tab 都是业务内容插槽而不是固定占位。业务可在 `panels[].content` 中直接传入任意 React 内容,或通过 `renderPanelContent({ panel, index, activeTabId })` 按当前 tab 动态渲染;允许承载托管助手 ChatMessage 执行流、用户画像、订单/商品详情、历史工单、工单日志、沟通记录、风险提示、售后策略、处理工具、表单、表格片段、图文/视频信息或自定义复合组件。",
7268
+ "【右侧信息 Tab 区自定义约束】自定义内容只能替换每个 tab 的内容区,不能替换 InfoDisplayPanel 的外框、Tabs、拆分/合并按钮、栏间拖拽、最小宽度、响应式列数和滚动容器。内容组件必须使用 `w-full min-w-0`、自动换行和内部滚动适配窄栏;禁止写固定大宽度、绝对定位撑破栏位,禁止为了展示自定义内容而改用多个 Card、自制 Tabs 或普通 div 拼出右侧信息栏。",
7238
7269
  "【拖拽与拆分保留验收】客服工作台生成页必须同时满足:拖动左侧列表边界时 ConversationList 默认列表 / 卡片列表容器实时适配;拖动右侧 InfoDisplayPanel 左边界时信息栏整体宽度变化且 IM 区自动吃剩余空间;拆出 InfoDisplayPanel tab 后,栏间分隔线可继续调节相邻栏宽,且每栏不小于 200px。任一能力缺失都视为没有正确使用客服工作台框架。",
7239
7270
  "【右侧插槽】如业务需要替换右侧主内容,可放 ChatConversationPattern、Table、表单详情或工单处理面板;但客服接待类页面优先保持“IM 对话 + InfoDisplayPanel”的组合,不要用多个普通 Card 临时拼出右侧信息区。",
7240
7271
  "【AI 选型】当 prompt 出现“客服工作台框架 / 客服工作台 / 客服在线工作台 / 在线客服工作台 / 在线 Agent / Agent 工作台 / 基础模式 / 托管模式 / 客服名称在线状态 / 顶部指标工具条”等客服工作台信号时,优先选本页面模板;但本模板右侧主内容仍必须包含 IMConversationPattern 聊天区和 InfoDisplayPanel 信息区。如果只是全局左侧导航,选 NavBar;如果只是会话队列,选 ConversationList;如果只是单条消息,选 ChatMessage / ChatBubble。",
@@ -7403,11 +7434,13 @@
7403
7434
  "rules": [
7404
7435
  "【五模板定位】本模板只用于“从 0 发起 AI 任务”的入口页:用户还没进入某个对象、会话或产物,第一步是输入一句需求、搜索助手、选择模板/案例/推荐 Agent。若用户要管理一批助手/模板/机器人列表,用 `BasePageFramePattern`;若用户已经在任务内持续追问,用 `ChatConversationPattern`;若右侧有主文档/主画布/主配置,AI 只是辅助,用 `CopilotPagePattern`;若是客服/私信线程,用 `IMConversationPattern`。",
7405
7436
  "【布局 Recipe】必须按 `LAYOUT_RECIPES.md` 选择 `chat-home`:右侧主内容区是 gray-direct,不是白色 `WorkSurface`;Hero / ChatInput / Tabs / 搜索 / 模板卡网格直接坐 `var(--color-blueGrey-200)`。",
7406
- "【组件选型门禁】主输入必须用 `ChatInput`,不可用原生 textarea 或 Input 冒充 AI 输入;模板/助手/案例推荐必须用 `Card`;分类切换必须用 `Tabs`;搜索框用 `Input` + `Icon` 前缀;无结果用 `Empty`。禁止用 Button/Tag 排一行冒充 Tabs,禁止手写卡片。",
7437
+ "【组件选型门禁】主输入必须用 `ChatInput`,不可用原生 textarea、InputForm 字段冒充 AI 输入;模板/助手/案例推荐必须用 `Card`;分类切换必须用 `Tabs`;搜索框用 `Input` + `Icon` 前缀;无结果用 `Empty`。禁止用 Button/Tag 排一行冒充 Tabs,禁止手写卡片。",
7407
7438
  "【整体背景】页面以浅灰大背景(`--color-blueGrey-200`)为主:Hero、筛选行、卡片网格**直接坐灰底**,不要再额外包一整张“右侧白色大卡片容器”(白卡只用于推荐卡片本身)。",
7408
7439
  "【反例扫描】如果同一右侧首页函数里同时出现 `ChatInput`、模板 `Card` 网格,并且外层存在 `background: var(--color-surface)` / `bg-surface rounded-*` 包住 Hero + 输入框 + Tabs + 网格,说明 AI 把入口页误生成成白卡管理页,必须拆掉这层 wrapper。",
7409
7440
  "【框架不动】外框灰底 + 左侧 `NavBar` 固定不变;右侧内容区必须 `flex-1 min-w-0 min-h-0 overflow-y-auto overflow-x-hidden`,由右侧内容区作为唯一滚动容器承载整体页面滚动;Hero、ChatInput、筛选行、卡片网格要一起被滑动,禁止只让下方卡片列表单独 `overflow-y-auto`。",
7410
7441
  "【Hero 区】居中欢迎标题 + 最大宽 680px 的 ChatInput 直接坐浅灰底;Hero 主标题使用 `text-4xl leading-10`;默认使用 `ChatInput variant=\"default\"` 作为入口主输入;标准节奏为 `padding: 84px 40px 80px`,标题区较旧版整体上移 36px。标题区副标题与 ChatInput 之间必须保留 48px 间距(比普通标题说明节奏额外增加 24px),保证输入框不贴近标题区。AI 渐变只用于输入框内部 / AI 标识,不作为整块 Hero 白底。",
7442
+ "【Hero 输入区禁止 Form】Hero 区的标题、副标题、AI 身份提示和主 ChatInput 都属于入口页居中启动区,不是表单字段。禁止用 `<Form />`、Form 的 `items[].label`、`labelAi` 或字段标题来包裹 / 模拟输入框顶部的“AI”标题;如果需要在输入框上方展示“AI / 助手名 / 问候语”,必须作为 Hero 内容用普通居中布局实现,并保持 `text-center`、`items-center`、`maxWidth: 680px` 的 ChatInput 容器。",
7443
+ "【Hero 反例扫描】同一 ChatHome 页面中如果出现 `<Form items={[{ label: \"AI\", type: \"input\" | \"textarea\" }]}>`、`labelAi` 或 Form 字段标题紧贴主 ChatInput 上方,说明 AI 把入口页主输入误生成为表单字段,必须删除 Form,改回居中 Hero 标题 + `<ChatInput variant=\"default\" />`。",
7411
7444
  "【筛选与分类】分类切换必须用 `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 下方单独占一行。",
7412
7445
  "【卡片列表】使用自适应 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` 造成只有卡片区滚动。",
7413
7446
  "【内容可扩展】Tab 项数量和卡片数据替换为业务真实数据;卡片 stats/tags 按业务维度自定义"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "system": "b-end",
3
3
  "skill": "tfds",
4
- "generatedAt": "2026-05-14T17:33:55.296Z",
4
+ "generatedAt": "2026-05-16T18:40:07.215Z",
5
5
  "purpose": "轻量组件与页面模板目录。AI 先读本文件做选型;确定命中组件后,再到 components.index.json 按 id 读取 props / rules / examples。",
6
6
  "counts": {
7
7
  "total": 47,
@@ -313,7 +313,7 @@
313
313
  "kind": "component",
314
314
  "name": "InfoDisplayPanel",
315
315
  "category": "business",
316
- "description": "信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并按业务动态 tab1 / tab2 / tab3 拆分为 1-3 栏,支持整体宽度与栏间宽度拖拽。",
316
+ "description": "信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并按业务动态 tab1 / tab2 / tab3 拆分为 1-3 栏,支持整体宽度与栏间宽度拖拽,且每个 tab 内容都可由业务完全自定义。",
317
317
  "import": {
318
318
  "from": "@tfdesign/b-end",
319
319
  "default": "InfoDisplayPanel"
@@ -344,8 +344,8 @@
344
344
  "多面板工作区",
345
345
  "panel tabs"
346
346
  ],
347
- "ruleCount": 28,
348
- "exampleCount": 8,
347
+ "ruleCount": 30,
348
+ "exampleCount": 10,
349
349
  "hasCode": false,
350
350
  "detailRef": "components.index.json#info-display-panel"
351
351
  },
@@ -600,7 +600,7 @@
600
600
  "kind": "component",
601
601
  "name": "Form",
602
602
  "category": "basic",
603
- "description": "B端表单组合组件,负责字段标题、说明、错误反馈与 top/left 布局;字段控件复用平台已有基础组件,覆盖 13 类表单字段。",
603
+ "description": "B端表单组合组件,本质是字段标题 / 说明 / 错误反馈与各类表单基础组件的组合层;字段控件完整复用对应基础组件的默认能力、交互、状态、尺寸和 AI 推荐能力。",
604
604
  "import": {
605
605
  "from": "@tfdesign/b-end",
606
606
  "default": "Form"
@@ -631,8 +631,8 @@
631
631
  "top 布局",
632
632
  "left 布局"
633
633
  ],
634
- "ruleCount": 20,
635
- "exampleCount": 8,
634
+ "ruleCount": 27,
635
+ "exampleCount": 11,
636
636
  "hasCode": false,
637
637
  "detailRef": "components.index.json#form"
638
638
  },
@@ -1636,7 +1636,7 @@
1636
1636
  "工单处理框架",
1637
1637
  "CustomerServiceWorkspaceFrame"
1638
1638
  ],
1639
- "ruleCount": 26,
1639
+ "ruleCount": 28,
1640
1640
  "exampleCount": 0,
1641
1641
  "hasCode": true,
1642
1642
  "detailRef": "components.index.json#customer-service-workspace-frame"
@@ -1718,7 +1718,7 @@
1718
1718
  "今天想做什么",
1719
1719
  "帮我快速搞定"
1720
1720
  ],
1721
- "ruleCount": 10,
1721
+ "ruleCount": 12,
1722
1722
  "exampleCount": 0,
1723
1723
  "hasCode": true,
1724
1724
  "detailRef": "components.index.json#chat-home-page"
@@ -67,7 +67,7 @@ const CARD_INTERACTIVE = [
67
67
  ].join(' ');
68
68
 
69
69
  const DATA_CARD = 'min-h-[var(--size-card-min-height)] flex-col justify-between gap-7';
70
- const PRODUCT_CARD = 'min-h-[96px] flex-row items-center gap-5 [&_.tfds-tag.bg-white]:hidden';
70
+ const PRODUCT_CARD = 'min-h-[96px] flex-row items-center gap-5 !rounded-lg [&_.tfds-tag.bg-white]:hidden';
71
71
  const INFO_CARD = 'min-h-[148px] flex-row items-start gap-6 p-6 [&_.tfds-tag.bg-white]:hidden';
72
72
 
73
73
  const CARD_VARIANT_CLASS = {
@@ -281,7 +281,7 @@ const ANIMATED_TONE_STYLE = {
281
281
 
282
282
  /* ── 信息卡片3 ── */
283
283
  const INFO3_CARD = [
284
- 'min-h-[148px] flex-col gap-2 rounded-lg p-4',
284
+ 'min-h-[148px] flex-col gap-2 !rounded-lg p-4',
285
285
  'text-left',
286
286
  ].join(' ');
287
287
  const INFO3_HEADER = 'flex items-start justify-between gap-2';
@@ -34,6 +34,7 @@ export const CARD_TOKEN_MAP = {
34
34
  ],
35
35
  商品卡片: [
36
36
  { label: '布局模式', cssProp: 'prop', value: 'infoLayout: 图标在左(default) / 图标在右(icon-right)' },
37
+ { label: '卡片圆角', cssProp: 'border-radius', token: '--radius-lg', value: '12px,使用 rounded-lg 覆盖通用卡片 16px' },
37
38
  { label: '图片尺寸', cssProp: 'width/height', value: '56px' },
38
39
  { label: '右侧文案区高度', cssProp: 'height', value: '56px' },
39
40
  { label: '图片圆角', cssProp: 'border-radius', token: '--radius-lg', value: '12px' },
@@ -30,7 +30,6 @@
30
30
  import { useState, useCallback, useRef, useLayoutEffect, useEffect, useMemo } from 'react';
31
31
  import AiSuggestionPanel, {
32
32
  AiRefreshButton,
33
- AI_REFRESH_VISIBLE_CLASS,
34
33
  buildSuggestionGroupsFromFlatList,
35
34
  } from './AiSuggestionShared';
36
35
 
@@ -58,7 +57,7 @@ function getTextAreaVerticalPaddingClass(minRows) {
58
57
 
59
58
  /* ── 根容器 ── */
60
59
  const WRAPPER_BASE = [
61
- 'inline-flex flex-col items-start w-[var(--size-input-width,100%)] max-w-full',
60
+ 'inline-flex flex-col items-start w-[var(--size-input-width,100%)] max-w-full gap-1',
62
61
  '[font-family:inherit]',
63
62
  ].join(' ');
64
63
 
@@ -119,7 +118,20 @@ const COUNT_ROW = 'flex w-full shrink-0 items-center justify-end px-3 pb-[6px] p
119
118
  const COUNT_TEXT_CLASS = 'text-xs leading-4 tabular-nums text-foreground-muted';
120
119
  const COUNT_TEXT_DISABLED = 'text-xs leading-4 tabular-nums text-foreground-disabled';
121
120
  const COUNT_OVERFLOW_CLASS = 'text-xs leading-4 tabular-nums text-red-500';
122
- const SUGGESTION_ACTION_ROW = 'flex w-full justify-end';
121
+ const AI_REFRESH_FLOATING_WRAPPER_CLASS = [
122
+ 'absolute top-2 right-2 z-[1] inline-flex',
123
+ 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100',
124
+ 'transition-opacity duration-150',
125
+ ].join(' ');
126
+
127
+ const AI_REFRESH_BUTTON_CLASS = [
128
+ 'inline-flex items-center justify-center shrink-0',
129
+ 'h-6 w-6 rounded-md',
130
+ 'bg-surface border border-border-default',
131
+ 'cursor-pointer p-0',
132
+ 'hover:border-border-strong',
133
+ 'transition-colors duration-150',
134
+ ].join(' ');
123
135
 
124
136
  const TEXTAREA_LOCAL_FALLBACK_SUGGESTION_GROUPS = [
125
137
  ['您好,这边已根据当前对话内容整理出一版备注草稿,您可以继续补充后保存。'],
@@ -268,6 +280,7 @@ export default function TextArea({
268
280
  FIELD_BASE,
269
281
  shouldFillHeight ? FIELD_FILL_HEIGHT : '',
270
282
  disabled ? DISABLED_CLASS : STATUS_CLASS[status],
283
+ !disabled && isCodeVariant && status === 'default' ? '!bg-blueGrey-100' : '',
271
284
  ].filter(Boolean).join(' ');
272
285
 
273
286
  const bodyCls = [
@@ -296,6 +309,14 @@ export default function TextArea({
296
309
  return (
297
310
  <div className={[`tfds-textarea`, wrapperCls].filter(Boolean).join(' ')} data-tfds-component="TextArea">
298
311
  <div className={fieldCls}>
312
+ {showAiRefresh ? (
313
+ <div className={AI_REFRESH_FLOATING_WRAPPER_CLASS}>
314
+ <AiRefreshButton
315
+ onClick={handleRefreshAiSuggestions}
316
+ className={AI_REFRESH_BUTTON_CLASS}
317
+ />
318
+ </div>
319
+ ) : null}
299
320
  {isCodeVariant ? (
300
321
  <div className={codeRowCls}>
301
322
  <div className={codeGutterCls} aria-hidden="true">
@@ -344,11 +365,6 @@ export default function TextArea({
344
365
  </div>
345
366
  ) : null}
346
367
  </div>
347
- {showAiRefresh ? (
348
- <div className={SUGGESTION_ACTION_ROW}>
349
- <AiRefreshButton onClick={handleRefreshAiSuggestions} className={AI_REFRESH_VISIBLE_CLASS} />
350
- </div>
351
- ) : null}
352
368
  {showSuggestion ? (
353
369
  <AiSuggestionPanel
354
370
  suggestions={suggestionList}
@@ -1310,7 +1310,7 @@ export const COMPONENTS = [
1310
1310
  name: 'InfoDisplayPanel',
1311
1311
  element: 'section',
1312
1312
  category: 'business',
1313
- description: '信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并按业务动态 tab1 / tab2 / tab3 拆分为 1-3 栏,支持整体宽度与栏间宽度拖拽。',
1313
+ description: '信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并按业务动态 tab1 / tab2 / tab3 拆分为 1-3 栏,支持整体宽度与栏间宽度拖拽,且每个 tab 内容都可由业务完全自定义。',
1314
1314
  componentFile: './components/InfoDisplayPanel.jsx',
1315
1315
  tokensFile: './components/InfoDisplayPanel.tokens.js',
1316
1316
  props: [
@@ -1344,7 +1344,7 @@ export const COMPONENTS = [
1344
1344
  },
1345
1345
  rules: [
1346
1346
  FONT_WEIGHT_RUNTIME_RULE,
1347
- '【组件定位】InfoDisplayPanel 是“信息展示面板框架”,只负责外框、顶部 Tabs、拆分/合并、分栏调宽、内容插槽和基础间距;它不提供业务内容默认占位,不替代 Card、Table、Form、ConversationList 或客服工作台整页框架。',
1347
+ '【组件定位】InfoDisplayPanel 是“信息展示面板框架”,只负责外框、顶部 Tabs、拆分/合并、分栏调宽、内容插槽和基础间距;它不提供业务内容默认占位,不替代 Card、Table、Form、ConversationList 或客服工作台整页框架。每个 tab 的正文区域是开放插槽,可承载任意业务自定义内容。',
1348
1348
  '【触发引用】当页面需要在同一工作区右侧并列查看“托管助手 / 历史工单 / 工单日志 / 信息工具 / 视频信息 / 用户信息 / 沟通记录”等多个详情面板,并且用户需要把当前关注的 tab 临时拆出来并排对照时,优先使用 InfoDisplayPanel;如果只是单块内容展示或普通 tabs 切换,不应使用该业务框架。',
1349
1349
  '【客服业务优先规则】只要页面触及客服业务、客服工作台、客服接待、工单处理、售后处理、订单/商品上下文核对、客服资料查看等场景,并需要展示客服工单信息、商品信息、订单信息、用户信息、客服相关信息或沟通记录等辅助信息板块,默认优先使用 InfoDisplayPanel 承载这些信息区;禁止用多个 Card、div 白卡或自制 tabs 临时拼出客服信息侧栏。',
1350
1350
  '【客服分类 Tabs】客服信息区涉及多个分类时,必须通过 InfoDisplayPanel 顶部 Tabs 切换分类,常见分类包括“工单信息 / 商品信息 / 订单信息 / 用户信息 / 客服信息 / 沟通记录 / 处理日志 / 风险提示”。分类名称、顺序和内容必须来自当前业务上下文;不要把这些示例硬编码成所有页面的固定 tab。',
@@ -1360,12 +1360,14 @@ export const COMPONENTS = [
1360
1360
  '【分栏门槛】每栏最小宽度固定 `200px`,双栏最小需求空间为 `401px`,三栏最小需求空间为 `602px`;小于 `401px` 时只能单栏,`401px-601px` 时最多双栏,`>= 602px` 时最多三栏。',
1361
1361
  '【拖拽调宽】组件整体宽度必须通过左外边界那 1 条手柄统一调整;当拆分为 2 栏或 3 栏后,组件内部只保留栏与栏之间的分隔线拖拽,用于调整左右相邻两栏宽度。禁止在组件最左侧再叠加第二条“第一栏宽度”手柄。所有栏位最小宽度统一为 `200px`,最大宽度不单独限制,只受外层容器可用宽度约束。',
1362
1362
  '【预览验收】组件预览默认撑满预览容器,并提供组件左侧边缘的整体宽度拖拽;单栏、双栏和三栏切换都应通过同一预览宽度拖拽链路验证,不再依赖固定宽度画板。',
1363
- '【默认内容】组件默认不渲染任何骨架屏、灰色占位卡片或示意内容;InfoDisplayPanel 只提供框架、Tabs、拆分/合并、拖拽调宽和内容插槽。业务内容必须由 `panels[].content` 或 `renderPanelContent` 注入。',
1363
+ '【默认内容】组件默认不渲染任何骨架屏、灰色占位卡片或示意内容;InfoDisplayPanel 只提供框架、Tabs、拆分/合并、拖拽调宽和内容插槽。业务内容必须由 `panels[].content` 或 `renderPanelContent({ panel, index, activeTabId })` 注入。',
1364
1364
  '【内容内距】每栏内容区四周内边距固定 16px,内容块之间纵向间距固定 16px;内容区默认 `flex-col` 排列,子元素默认 `min-width: 0; width: 100%`,需要跟随当前 tab 栏宽度自动伸缩适配。每栏内容区独立滚动,内容多时只滚动当前栏,不撑高外层面板。AI 生成内容时应优先使用响应式宽度、自动换行和内部滚动,避免 `w-[固定值]`、绝对定位和固定大宽度表格。',
1365
1365
  '【Tabs规范】每栏顶部使用页签语义组织,统一使用基础 `Tabs variant="line" size="lg"`;Header 横向内边距为 16px,Tabs 文案之间保持基础 Tabs 的 item padding 与间距,不额外手写按钮间距。右侧动作按钮与 Tabs 区域保持 12px 间距。选中态为基础 Tabs 的 semibold + Brand 500 3px 指示线;未选中态为基础 Tabs 的 normal 字重和次级色。主栏展示未拆分 tabs 集合,拆分栏只展示各自承载的单个 tab。Tabs 只切换本栏内容,不改变页面路由。',
1366
1366
  '【单Tab降级】当业务只有 1 个分类时,容器不应为了形式感渲染 Tabs;运行时会用 `FormTitle variant="card"` 展示该分类标题,内容区仍按 16px 内距承载业务组件。AI 生成页面时,如果只有单个分类,应优先直接使用标题 + 内容结构,除非后续明确会扩展为多 tab。',
1367
- '【内容承载】每一个 tab 栏都可以承载任意业务组件元素,包括信息卡片、表单、表格片段、日志列表、图片/视频信息、实验命中详情或自定义复合组件;被承载组件必须优先使用 `w-full min-w-0` 等自适应写法,避免固定宽度导致拆分栏变窄时溢出。',
1368
- '【AI生成约束】AI 生成具体业务页面时,应把真实信息卡片、表单、工单日志或内容详情放进 `panels[].content` `renderPanelContent`;不要手写外层分栏容器、不要用多个 Card + gap 临时拼出三栏框架,也不要依赖 InfoDisplayPanel 的默认占位内容。',
1367
+ '【内容承载】每一个 tab 栏都可以承载任意业务组件元素,包括 ChatMessage 托管助手、信息卡片、表单、表格片段、日志列表、图片/视频信息、实验命中详情、风险提示、订单/商品详情、售后策略、处理工具或自定义复合组件;被承载组件必须优先使用 `w-full min-w-0`、自动换行和内部滚动等自适应写法,避免固定宽度导致拆分栏变窄时溢出。',
1368
+ '【自定义接入方式】简单场景优先把内容直接放在 `panels[].content`:例如 `{ id: "order", tabs: [{ id: "order", label: "订单信息" }], content: <OrderInfo data={currentOrder} /> }`。复杂场景使用 `renderPanelContent` `panel.id` / `activeTabId` 动态返回内容,适合多个 tab 共享同一个当前会话上下文、按 tab 懒渲染,或把内容生成逻辑集中管理。',
1369
+ '【客服工作台自定义内容】嵌入客服工作台时,InfoDisplayPanel 的 tab 内容必须由外层当前 `activeConversationId` / `currentTicketId` 派生后注入,确保托管助手、用户画像、历史工单、工单日志、商品/订单信息、沟通记录等内容和左侧 IM 聊天区指向同一处理对象;不要让信息面板内部自行维护另一套当前对象状态。',
1370
+ '【AI生成约束】AI 生成具体业务页面时,应把真实信息卡片、表单、工单日志、ChatMessage 托管助手或内容详情放进 `panels[].content` 或 `renderPanelContent`;不要手写外层分栏容器、不要用多个 Card + gap 临时拼出三栏框架,也不要依赖 InfoDisplayPanel 的默认占位内容。',
1369
1371
  '【组合关系】InfoDisplayPanel 可作为 CustomerServiceWorkspaceFrame 的 mainPanel 内容;放入主白卡时宽高使用 `size-full` 或父容器约束,外层不要再套一层大白卡。',
1370
1372
  '【嵌入客服工作台时的语义】在客服工作台框架模版里,InfoDisplayPanel 不是独立静态侧栏,而是“当前左侧选中会话 / 工单”的辅助信息区。左侧会话列表默认选中项变化时,InfoDisplayPanel 内各 tab 的内容也必须同步切换到该当前对象对应的数据上下文,例如用户信息、历史工单、工单日志、视频信息、沟通记录都应属于同一个当前处理对象。',
1371
1373
  '【联动边界】客服工作台里的右侧白卡应被视为一个整体工作区:左侧 IM 对话区负责当前线程,右侧 InfoDisplayPanel 负责同一线程的辅助信息。两者由外层模板统一接收当前对象 id 并同步刷新;InfoDisplayPanel 不应自己脱离左侧上下文单独维持另一份“当前对象”。',
@@ -1377,6 +1379,37 @@ export const COMPONENTS = [
1377
1379
  { label: '监听拆分状态', code: '<InfoDisplayPanel onSplitChange={({ splitTabIds, visibleColumns }) => console.log(splitTabIds, visibleColumns)} />' },
1378
1380
  { label: '可选限制最大栏数', code: '<InfoDisplayPanel defaultColumnCount={2} />' },
1379
1381
  { label: '自定义面板', code: '<InfoDisplayPanel panels={[{ id: "assistant", tabs: [{ id: "overview", label: "托管助手" }], content: <AssistantPanel /> }, { id: "history", tabs: [{ id: "tickets", label: "历史工单" }], content: <HistoryTickets /> }]} />' },
1382
+ { label: '客服工作台自定义信息 Tabs', code: `const infoPanels = [
1383
+ {
1384
+ id: "assistant",
1385
+ tabs: [{ id: "assistant", label: "托管助手" }],
1386
+ content: <ChatMessage role="ai" taskGroups={assistantFlow} resultText={recommendedReply} />,
1387
+ },
1388
+ {
1389
+ id: "order",
1390
+ tabs: [{ id: "order", label: "订单信息" }, { id: "risk", label: "风险提示" }],
1391
+ content: <OrderInfoPanel conversationId={activeConversationId} />,
1392
+ },
1393
+ {
1394
+ id: "records",
1395
+ tabs: [{ id: "history", label: "历史工单" }, { id: "logs", label: "工单日志" }],
1396
+ content: <TicketRecordPanel ticketId={currentTicketId} />,
1397
+ },
1398
+ ];
1399
+
1400
+ <InfoDisplayPanel panels={infoPanels} className="size-full" />` },
1401
+ { label: '动态渲染 Tab 内容', code: `<InfoDisplayPanel
1402
+ panels={[
1403
+ { id: "assistant", tabs: [{ id: "assistant", label: "托管助手" }] },
1404
+ { id: "context", tabs: [{ id: "user", label: "用户信息" }, { id: "order", label: "订单信息" }] },
1405
+ ]}
1406
+ renderPanelContent={({ panel, activeTabId }) => {
1407
+ if (panel.id === "assistant") return <AssistantPanel conversationId={activeConversationId} />;
1408
+ if (activeTabId === "user") return <UserProfile userId={currentUserId} />;
1409
+ if (activeTabId === "order") return <OrderInfo orderId={currentOrderId} />;
1410
+ return null;
1411
+ }}
1412
+ />` },
1380
1413
  { label: '单分类自动降级为标题', code: '<InfoDisplayPanel panels={[{ id: "profile", tabs: [{ id: "profile", label: "用户信息" }], content: <UserProfile /> }]} />' },
1381
1414
  { label: '放入客服工作台主面板', code: '<CustomerServiceWorkspaceFrame mainPanel={<InfoDisplayPanel className="size-full" />} />' },
1382
1415
  { label: '❌ Bad(手搓三栏信息区)', code: '/* 禁止!缺少 200px 最小栏宽判断、主栏固定最右与 tab 拆分/合并逻辑 */\n<div className="flex gap-4"><Card /><Card /><Card /></div>' },
@@ -1996,7 +2029,7 @@ export const COMPONENTS = [
1996
2029
  name: 'Form',
1997
2030
  element: 'form',
1998
2031
  category: 'basic',
1999
- description: 'B端表单组合组件,负责字段标题、说明、错误反馈与 top/left 布局;字段控件复用平台已有基础组件,覆盖 13 类表单字段。',
2032
+ description: 'B端表单组合组件,本质是字段标题 / 说明 / 错误反馈与各类表单基础组件的组合层;字段控件完整复用对应基础组件的默认能力、交互、状态、尺寸和 AI 推荐能力。',
2000
2033
  componentFile: './components/Form.jsx',
2001
2034
  tokensFile: './components/Form.tokens.js',
2002
2035
  props: [
@@ -2042,8 +2075,9 @@ export const COMPONENTS = [
2042
2075
  },
2043
2076
  _preview: FORM_PREVIEW,
2044
2077
  rules: [
2045
- '【组件定位】Form 是字段组合层,只管理 label、提示、错误反馈、top/left 布局和字段编排,不重新定义已有表单控件样式',
2046
- '【控件复用】已有基础组件必须复用:input 复用 Input,textarea 复用 TextArea(仅传基组件已有 props,默认 minRows=3、resize 纵向增高与 TextArea 一致;System Prompt / JSON / 代码参数字段必须传 variant="code"),input-number 复用 InputNumber,select 复用 Select,tag-input 复用 TagInput,checkbox 复用 CheckboxGroup,radio 复用 RadioGroup,switch 复用 Switch,date-picker 复用 DatePicker,time-picker 复用 TimePicker,slider 复用 Slider,upload 复用 Upload',
2078
+ '【组件定位】Form 是字段组合层,本质是 `FormTitle` / 字段标题 / 提示 / 错误反馈 / top-left 布局 + 各类型表单基础组件的组合;它不重新定义已有表单控件样式、交互或能力,也不是一套新的输入控件体系。',
2079
+ '【控件复用】已有基础组件必须复用:input 复用 Input,textarea 复用 TextArea(仅传基组件已有 props,默认 minRows=3、resize 纵向增高与 TextArea 一致;System Prompt / JSON / 代码参数字段必须传 variant="code"),input-number 复用 InputNumber,select 复用 Select,tag-input 复用 TagInput,checkbox 复用 CheckboxGroup,radio 复用 RadioGroup,switch 复用 Switch,date-picker 复用 DatePicker,time-picker 复用 TimePicker,slider 复用 Slider,upload 复用 Upload',
2080
+ '【能力继承】Form 中字段类型对应哪个基础组件,就完整继承该基础组件的默认能力、交互、状态、尺寸、token、禁用态、错误态、值受控方式和回调语义;Form 只负责把 label / helpText / errorText / required 等字段外壳组合起来,不允许在 Form 内把同类型控件做成与基础组件不同的行为。',
2047
2081
  '【布局】Form 在页面主内容区、白色卡片容器与纵向堆叠场景中默认 `w-full min-w-0 self-stretch`;labelPosition="top" 时字段容器与控件区都默认全宽,label 与控件间距 4px;labelPosition="left" 时标签列固定 96px、间距 24px、右侧控件区使用 `flex-1 min-w-0 w-full` 撑满剩余宽度',
2048
2082
  '【正文表单宽度铁律】页面主表单、白卡/卡片内配置表单、澄清确认卡片、抽屉/弹窗正文表单、侧栏详情表单中,Input / Select / TextArea / InputNumber / DatePicker / TimePicker / TagInput / Slider / InputGroup 这类字段型控件必须默认撑满字段内容列,且同一表单区域左右边界对齐、宽度一致。多个字段上下排列在同一卡片容器内时,每个字段都应随卡片内容区动态撑满,不受 240px / 200px / 320px 的筛选搜索宽度限制。',
2049
2083
  '【筛选工具条宽度例外】只有列表筛选栏、工具条、紧凑查询区、AI 首页模板搜索、Tabs 右侧搜索这类横向辅助查询入口,才允许固定中窄宽:搜索 Input 默认约 240px、最小 200px、最大 320px;Select / DatePicker / TimePicker / TagInput 等筛选控件常用 160-240px(日期时间范围可更宽)。禁止把筛选工具条固定宽套到业务表单正文。',
@@ -2051,12 +2085,18 @@ export const COMPONENTS = [
2051
2085
  '【紧凑/特殊控件宽度】Switch、普通 CheckboxGroup、普通 RadioGroup 的控件本体按语义保持自然宽度,但字段容器仍占满表单区域并与其它字段左边界对齐;卡片型 Radio/Checkbox 选项可按场景设置 `w-full` 形成等宽选项。Upload 字段区域占满表单区域,内部图片 tile 保持固定尺寸并自动换行,不强行拉伸 tile。',
2052
2086
  '【自定义控件宽度】使用 children 或 item.control 传入自定义字段时,外层必须补 `className="w-full min-w-0 max-w-full"`;否则会绕过 Form 内置的 `!w-full` 控件宽度约束,导致 Input、Select、TextArea 等宽度不一致',
2053
2087
  '【详情页全集预览】组件详情页的“全集”分类按字段类型从上到下单列排列,每个组件间距 40px,超出预览画布时在画布内滚动查看',
2054
- '【字段配置】items 数组每项支持 id、label、type、required、labelPosition、middleHelpText、helpText、error、errorText、disabled、value/defaultValue/onChange 等字段;当字段命中 AI 推荐语义时,可继续透传 aiSuggestion、aiSuggestions、onAdoptSuggestion、onRefreshAiSuggestions Input / Select / TextArea',
2088
+ '【字段配置】items 数组每项支持 id、label、type、required、labelPosition、middleHelpText、helpText、error、errorText、disabled、value/defaultValue/onChange 等字段;字段项上未被 Form 消费的基础组件 props 应继续透传给对应控件,保证 Form 内控件能力与单独使用基础组件时一致。',
2089
+ '【AI推荐能力继承】如果某个字段类型对应的基础组件支持 AI 推荐,那么 Form 中同类型字段必须拥有完全一致的 AI 推荐能力:type="input" 继承 Input 的 aiSuggestion / aiSuggestions / onAdoptSuggestion / onRefreshAiSuggestions;type="textarea" 继承 TextArea 的同名能力;type="select" 在默认 Select 模式下继承 Select 的 AI 推荐能力。Form 不单独创造新的 AI 推荐 UI,也不改变推荐区位置、间距、hover、刷新、采纳、写入值和回调行为。',
2090
+ '【AI推荐标题标签】当 Form 的某个字段启用 AI 推荐能力时,该字段标题右侧默认必须展示标准“AI”标签,等价于自动开启 labelAi;AI 标签用于提示该字段正在由智能推荐辅助填写,不需要业务额外再手动配置标题-AI。若字段未启用 AI 推荐,则 labelAi 仍可按普通标题附加样式独立配置。',
2091
+ '【AI推荐标题标签边界】Form 的 `labelAi` 只适用于 Form 内部字段标题,不能外溢为页面 Hero 标题、ChatInput 顶部身份提示或 AI 入口页标题。AI 入口页、AI 对话页、ChatInput Hero 区、消息输入区顶部的“AI / 助手名 / 问候语”都不得用 Form 或 Form 字段 label 实现,应遵守对应页面模板和 ChatInput 规范。',
2092
+ '【AI推荐配置预览】组件详情页左侧配置区必须在字段类型支持 AI 推荐时展示“AI推荐”显隐配置项:当字段类型为 Input / TextArea / Select(默认模式)时可切换显示或隐藏推荐区,并实时预览对应基础组件的 AI 推荐交互;当画布预览区显示 AI 推荐时,下方“标题-AI”开关必须自动打开并处于选中态;切换到不支持 AI 推荐的字段类型或“全集”预览时,该配置项必须隐藏且不向字段透传 AI 推荐 props。',
2093
+ '【AI推荐透传边界】不支持 AI 推荐的基础组件(如 InputNumber、TagInput、CheckboxGroup、RadioGroup、Switch、DatePicker、TimePicker、Slider、Upload 等)在 Form 中也不应额外展示 AI 推荐区;Select 的 tag 模式仍遵循 Select 自身规则,不支持 AI 推荐。AI 生成 Form 时,只有需求明确出现“AI推荐 / 智能推荐 / 建议填写 / 推荐文案 / 推荐选项”等语义,才给对应字段透传 AI 推荐 props。',
2094
+ '【不适用场景】Form 不用于承载 AI 入口页 Hero 主输入、AI 对话页底部输入、IM/客服消息输入、ChatInput 顶部猫条或输入框上方身份提示;这些场景必须直接使用 `ChatInput` 或对应页面模板。若只是为了显示“AI”标签或标题而使用 Form,属于错误选型。',
2055
2095
  '【标题附加样式】单字段标题支持 labelOptional、labelRequired、labelAi、labelHelp 四个布尔开关,可与纯文字标题组合显示;渲染顺序固定为 可选 -> 必填 -> AI -> Help',
2056
2096
  '【标题字重】字段标题、必填星号与可选文案统一使用 `semibold` token,即 `[font-weight:var(--font-semibold)]`;不要继续使用 `font-bold` 或手写 700 字重',
2057
2097
  '【AI 标签】当 labelAi=true 时,必须复用标准 `<Tag variant="ai" radius="full" fontWeight="bold" size="m">AI</Tag>`;禁止在 Form 内手写渐变胶囊、手写圆角块或用普通文字 span 模拟 AI 标签',
2058
2098
  '【标题必填】labelRequired 用于标题星号装饰;若未单独配置,仍可通过 required + requiredMark 复用现有星号逻辑',
2059
- '【配置联动】组件详情页切换字段类型时,左侧配置区需动态追加当前被引用基础组件自身的枚举配置,并透传到对应字段控件(textarea 与 TextArea 详情配置侧栏一致:内容、超长计数、状态、默认高度、状态、高度适配)',
2099
+ '【配置联动】组件详情页切换字段类型时,左侧配置区需动态追加当前被引用基础组件自身的枚举配置,并透传到对应字段控件(textarea 与 TextArea 详情配置侧栏一致:内容、超长计数、状态、默认高度、状态、高度适配);AI 推荐显隐也必须按字段类型动态出现/隐藏。',
2060
2100
  '【TagInput 字段】Form 中 type="tag-input" 的标签颜色固定为灰色、Tag 尺寸固定为 M,不暴露颜色或尺寸配置',
2061
2101
  '【反馈优先级】error=true 时展示红色图标 + 错误文案,并将可复用控件 status 设为 error;无错误时展示 helpText',
2062
2102
  '【未独立组件】InputGroup 尚未作为独立基础组件存在,Form 内部仅做轻量组合展示;如后续沉淀为基础组件,应替换为独立组件引用',
@@ -2069,6 +2109,9 @@ export const COMPONENTS = [
2069
2109
  { label: '错误反馈', code: '<Form items={[{ id: "email", label: "邮箱", type: "input", error: true, errorText: "该项目为必填项" }]} />' },
2070
2110
  { label: '标题附加样式', code: '<Form items={[{ id: "name", label: "字段标题", type: "input", labelOptional: true, labelAi: true, labelHelp: true }]} />' },
2071
2111
  { label: '选择器字段', code: '<Form items={[{ id: "city", label: "城市", type: "select", options: [{ value: "hz", label: "杭州" }] }]} />' },
2112
+ { label: 'AI推荐 · Input字段', code: '<Form items={[{ id: "reply", label: "推荐回复", type: "input", aiSuggestions: ["您好,正在为您核实,请稍候。", "抱歉给您带来不便,这边已经帮您加急处理。"], onAdoptSuggestion: (suggestion) => console.log(suggestion), onRefreshAiSuggestions: () => {} }]} />' },
2113
+ { label: 'AI推荐 · TextArea字段', code: '<Form items={[{ id: "remark", label: "处理备注", type: "textarea", aiSuggestions: ["建议优先整理用户诉求、处理时效和需补充的材料信息。", "如果需要后续交接,可一并记录当前判责结论和预计回访时间。"], onAdoptSuggestion: (suggestion) => console.log(suggestion), onRefreshAiSuggestions: () => {} }]} />' },
2114
+ { label: 'AI推荐 · Select字段', code: '<Form items={[{ id: "category", label: "问题分类", type: "select", options: [{ value: "refund", label: "退款问题" }, { value: "invoice", label: "发票问题" }, { value: "account", label: "账号问题" }], aiSuggestions: ["退款问题", "账号问题"], onAdoptSuggestion: (value, option) => console.log(value, option), onRefreshAiSuggestions: () => {} }]} />' },
2072
2115
  { label: '全量示例', code: '<Form className="!gap-10" />' },
2073
2116
  { label: '❌ Bad(手搓 form + label)', code: '/* 禁止!手搓表单缺统一 label 间距、必填星号、错误反馈 */\n<form><label>姓名</label><input /></form>' },
2074
2117
  { label: '✅ Good(用 Form 组件)', code: '<Form items={[{ id: "name", label: "姓名", type: "input", required: true }]} />' },
@@ -2,6 +2,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import CustomerServiceWorkspaceFrame from '../components/CustomerServiceWorkspaceFrame';
3
3
  import ConversationList, { CONVERSATION_LIST_SAMPLE_SECTIONS } from '../components/ConversationList';
4
4
  import InfoDisplayPanel from '../components/InfoDisplayPanel';
5
+ import ChatMessage, { useStreamingTaskGroups } from '../components/ChatMessage';
6
+ import Button from '../components/Button';
7
+ import Icon from '../components/Icon';
5
8
  import { getTeamMemberByName } from '../teamMembers';
6
9
  import IMConversationPattern from './IMConversationPattern';
7
10
 
@@ -251,6 +254,176 @@ function buildConversationCardPreviewMessages(messages = []) {
251
254
  }));
252
255
  }
253
256
 
257
+ /**
258
+ * 客服托管助手 Mock:
259
+ * 完整还原一轮"用户进线 → AI 思考 → 推荐回复"的流程:
260
+ * 1. 顶部展示一条与左侧 IM 完全一致的用户消息气泡(取剧本中最近一条 user 消息)
261
+ * 2. 中部执行流(理解 → 检索 → 生成)按 600ms 节奏流式刷出
262
+ * 3. 末尾推荐回复文本与该用户消息之后客服在左侧实际发出的回复保持一致
263
+ */
264
+
265
+ function buildTrustModeTaskGroups(latestUserText) {
266
+ const summary = (latestUserText || '').trim();
267
+ const truncated = summary.length > 36 ? `${summary.slice(0, 36)}…` : summary;
268
+ const firstStepDetail = truncated
269
+ ? `User_Intent_Agent /msg "${truncated}"`
270
+ : 'User_Intent_Agent /msg/latest';
271
+ return [
272
+ {
273
+ id: 'trust-understand',
274
+ title: '理解用户最新消息与诉求',
275
+ status: 'completed',
276
+ defaultExpanded: true,
277
+ steps: [
278
+ {
279
+ id: 'trust-understand-parse',
280
+ title: '解析用户最新消息中的核心诉求与情绪',
281
+ actionLabel: '正在调用意图识别 Agent',
282
+ actionDetail: firstStepDetail,
283
+ actionIconName: 'message-question-circle-stroked',
284
+ },
285
+ ],
286
+ },
287
+ {
288
+ id: 'trust-retrieve',
289
+ title: '检索规则、订单上下文与会话历史',
290
+ status: 'completed',
291
+ defaultExpanded: true,
292
+ steps: [
293
+ {
294
+ id: 'trust-retrieve-rules',
295
+ title: '匹配平台售后规则与商家补充条款',
296
+ actionLabel: '正在调用知识库 Agent',
297
+ actionDetail: 'Knowledge_Agent /policy/match',
298
+ actionIconName: 'line-chart-up-04-stroked',
299
+ },
300
+ {
301
+ id: 'trust-retrieve-context',
302
+ title: '汇总订单状态与近 N 轮会话上下文',
303
+ actionLabel: '正在调用上下文 Agent',
304
+ actionDetail: 'Context_Agent /thread/recent',
305
+ actionIconName: 'line-chart-up-04-stroked',
306
+ },
307
+ ],
308
+ },
309
+ {
310
+ id: 'trust-compose',
311
+ title: '生成推荐回复口径',
312
+ status: 'processing',
313
+ defaultExpanded: true,
314
+ steps: [
315
+ {
316
+ id: 'trust-compose-tone',
317
+ title: '校准回复语气:先安抚 → 同步处理 → 给确定方案',
318
+ actionLabel: '正在调用话术 Agent',
319
+ actionDetail: 'Tone_Agent /reply/calibrate',
320
+ actionIconName: 'sticker-square-stroked',
321
+ },
322
+ {
323
+ id: 'trust-compose-draft',
324
+ title: '生成可一键发送的推荐回复文本',
325
+ actionLabel: '正在调用生成 Agent',
326
+ actionDetail: 'Reply_Agent /reply/draft',
327
+ actionIconName: 'code-square-01-stroked',
328
+ },
329
+ ],
330
+ },
331
+ ];
332
+ }
333
+
334
+ /**
335
+ * 找到剧本中最近一条 user 消息,并取其后第一条 agent 回复作为"客服侧已发出的回复"
336
+ * (保证托管助手推荐文案与左侧 IM 客服气泡完全一致)。
337
+ */
338
+ function getLatestUserTurn(messages = []) {
339
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
340
+ if (messages[i]?.kind === 'user') {
341
+ const userText = messages[i].text || '';
342
+ let agentReply = '';
343
+ for (let j = i + 1; j < messages.length; j += 1) {
344
+ if (messages[j]?.kind === 'agent') {
345
+ agentReply = messages[j].text || '';
346
+ break;
347
+ }
348
+ }
349
+ return { userText, agentReply };
350
+ }
351
+ }
352
+ return { userText: '', agentReply: '' };
353
+ }
354
+
355
+ const TRUST_MODE_FALLBACK_REPLY = '已读取最新一条用户消息并完成上下文检索,建议先安抚情绪、确认核心诉求,再按平台规则给出统一处理口径。';
356
+
357
+ function isStreamingFinished(streamed, fullGroups) {
358
+ if (!Array.isArray(streamed) || !Array.isArray(fullGroups)) return false;
359
+ if (streamed.length !== fullGroups.length) return false;
360
+ const lastStreamed = streamed[streamed.length - 1];
361
+ const lastFull = fullGroups[fullGroups.length - 1];
362
+ if (!lastStreamed || !lastFull) return false;
363
+ return (
364
+ lastStreamed.status === 'completed'
365
+ && (lastStreamed.steps?.length ?? 0) >= (lastFull.steps?.length ?? 0)
366
+ );
367
+ }
368
+
369
+ function TrustModeAssistantMessage({ threadId, latestUserText, recommendedReply, onCopyToInput, onSendToUser }) {
370
+ const fullTaskGroups = useMemo(
371
+ () => buildTrustModeTaskGroups(latestUserText),
372
+ [latestUserText],
373
+ );
374
+ const streamed = useStreamingTaskGroups(fullTaskGroups, { intervalMs: 600 });
375
+ const finished = isStreamingFinished(streamed, fullTaskGroups);
376
+
377
+ return (
378
+ <div className="flex flex-col gap-3" key={threadId}>
379
+ {latestUserText ? (
380
+ <ChatMessage
381
+ role="user"
382
+ userBubbleTone="fill"
383
+ userContent={[{ type: 'text', value: latestUserText }]}
384
+ />
385
+ ) : null}
386
+ <ChatMessage
387
+ role="ai"
388
+ header={{ name: 'HiAI' }}
389
+ title=""
390
+ steps={null}
391
+ taskGroups={streamed}
392
+ />
393
+ {finished ? (
394
+ <ChatMessage
395
+ role="ai"
396
+ title=""
397
+ steps={null}
398
+ resultText={recommendedReply}
399
+ />
400
+ ) : null}
401
+ {finished ? (
402
+ <div className="flex items-center gap-1">
403
+ <Button
404
+ type="button"
405
+ variant="ghost-black"
406
+ size="sm"
407
+ iconOnly
408
+ tooltip="复制到输入框"
409
+ icon={<Icon name="copy-06-stroked" size="sm" color="currentColor" aria-hidden="true" />}
410
+ onClick={() => onCopyToInput?.(recommendedReply)}
411
+ />
412
+ <Button
413
+ type="button"
414
+ variant="ghost-black"
415
+ size="sm"
416
+ iconOnly
417
+ tooltip="发送给用户"
418
+ icon={<Icon name="send-03-stroked" size="sm" color="currentColor" aria-hidden="true" />}
419
+ onClick={() => onSendToUser?.(recommendedReply)}
420
+ />
421
+ </div>
422
+ ) : null}
423
+ </div>
424
+ );
425
+ }
426
+
254
427
  function buildWorkspaceConversationSections(sections = WORKSPACE_CONVERSATION_SECTIONS) {
255
428
  return sections.map((section) => ({
256
429
  ...section,
@@ -295,6 +468,61 @@ export default function CustomerServiceWorkspaceFramePattern() {
295
468
  [activeConversationItem],
296
469
  );
297
470
  const activeMessages = WORKSPACE_THREAD_CONTENT[activeThread.id]?.messages || [];
471
+ const { userText: latestUserText, agentReply: scriptAgentReply } = useMemo(
472
+ () => getLatestUserTurn(activeMessages),
473
+ [activeMessages],
474
+ );
475
+ const recommendedReply = scriptAgentReply || TRUST_MODE_FALLBACK_REPLY;
476
+ const [composerPrefill, setComposerPrefill] = useState({ text: '', seed: 0 });
477
+ const [autoSendSignal, setAutoSendSignal] = useState({ text: '', seed: 0 });
478
+
479
+ /* 切换会话时清空待回填/待发送信号,避免历史信号影响新会话 */
480
+ useEffect(() => {
481
+ setComposerPrefill({ text: '', seed: 0 });
482
+ setAutoSendSignal({ text: '', seed: 0 });
483
+ }, [activeThread.id]);
484
+
485
+ const handleAssistantCopy = useCallback((text) => {
486
+ setComposerPrefill((prev) => ({ text: text || '', seed: prev.seed + 1 }));
487
+ }, []);
488
+ const handleAssistantSend = useCallback((text) => {
489
+ setAutoSendSignal((prev) => ({ text: text || '', seed: prev.seed + 1 }));
490
+ }, []);
491
+
492
+ const infoPanels = useMemo(
493
+ () => [
494
+ {
495
+ id: 'assistant',
496
+ tabs: [{ id: 'assistant', label: '托管助手' }],
497
+ content: (
498
+ <TrustModeAssistantMessage
499
+ threadId={activeThread.id}
500
+ latestUserText={latestUserText}
501
+ recommendedReply={recommendedReply}
502
+ onCopyToInput={handleAssistantCopy}
503
+ onSendToUser={handleAssistantSend}
504
+ />
505
+ ),
506
+ },
507
+ {
508
+ id: 'tickets',
509
+ tabs: [
510
+ { id: 'history', label: '历史工单' },
511
+ { id: 'logs', label: '工单日志' },
512
+ { id: 'tools', label: '信息工具' },
513
+ ],
514
+ },
515
+ {
516
+ id: 'info',
517
+ tabs: [
518
+ { id: 'video', label: '视频信息' },
519
+ { id: 'user', label: '用户命中实验' },
520
+ { id: 'records', label: '沟通记录' },
521
+ ],
522
+ },
523
+ ],
524
+ [activeThread.id, latestUserText, recommendedReply, handleAssistantCopy, handleAssistantSend],
525
+ );
298
526
 
299
527
  const handleConversationLayoutWidthRequest = useCallback((contentWidth) => {
300
528
  setSideWidth(contentWidth + PANEL_OVERLAP);
@@ -407,6 +635,8 @@ export default function CustomerServiceWorkspaceFramePattern() {
407
635
  style={{ height: '100%' }}
408
636
  thread={activeThread}
409
637
  messages={activeMessages}
638
+ composerPrefill={composerPrefill}
639
+ autoSendSignal={autoSendSignal}
410
640
  />
411
641
  </div>
412
642
  <div className={INFO_PANEL_FRAME} style={{ width: `${currentInfoPanelWidth}px` }}>
@@ -424,6 +654,7 @@ export default function CustomerServiceWorkspaceFramePattern() {
424
654
  <span className={INFO_PANEL_RESIZE_HANDLE_LINE} aria-hidden="true" />
425
655
  </button>
426
656
  <InfoDisplayPanel
657
+ panels={infoPanels}
427
658
  className="!h-full w-full"
428
659
  style={{ height: '100%' }}
429
660
  />
@@ -201,6 +201,10 @@ export default function IMConversationPattern({
201
201
  messages,
202
202
  className = '',
203
203
  style,
204
+ /* 外部驱动:把指定文本回填到底部输入框(不发送),由 seed 触发 */
205
+ composerPrefill = null,
206
+ /* 外部驱动:把指定文本作为人工客服消息直接发出(追加到对话),由 seed 触发 */
207
+ autoSendSignal = null,
204
208
  } = {}) {
205
209
  const [runtimeMessages, setRuntimeMessages] = useState([]);
206
210
  const scrollRef = useRef(null);
@@ -276,6 +280,24 @@ export default function IMConversationPattern({
276
280
  onSend?.(content, ctx);
277
281
  }, [onSend]);
278
282
 
283
+ /* 外部 autoSendSignal 触发:把推荐回复直接作为人工客服消息追加到对话 */
284
+ useEffect(() => {
285
+ if (!autoSendSignal || !autoSendSignal.seed) return;
286
+ const content = (autoSendSignal.text || '').trim();
287
+ if (!content) return;
288
+ setRuntimeMessages((prev) => [
289
+ ...prev,
290
+ {
291
+ id: `runtime-agent-auto-${autoSendSignal.seed}-${prev.length}`,
292
+ kind: 'agent',
293
+ text: content,
294
+ time: formatCurrentTimestamp(),
295
+ },
296
+ ]);
297
+ onSend?.(content, { source: 'trust-mode' });
298
+ // eslint-disable-next-line react-hooks/exhaustive-deps
299
+ }, [autoSendSignal?.seed]);
300
+
279
301
  return (
280
302
  <div
281
303
  className={['flex w-full min-w-0 flex-col overflow-hidden', className].filter(Boolean).join(' ')}
@@ -306,6 +328,8 @@ export default function IMConversationPattern({
306
328
  placeholder={resolvedComposerPlaceholder}
307
329
  acceptFiles={resolvedComposerVariant === 'im-basic' ? 'image/*' : '*'}
308
330
  onSend={handleSend}
331
+ prefillText={composerPrefill?.text || ''}
332
+ prefillSeed={composerPrefill?.seed || 0}
309
333
  />
310
334
  </div>
311
335
  )}
@@ -59,6 +59,8 @@ export const PATTERNS = [
59
59
  '【状态同步原则】当业务接真实数据、URL 或埋点时,应以左侧当前选中对象 id 作为单一事实源(single source of truth),再派生右侧聊天流和信息面板内容;不要让 IMConversationPattern 和 InfoDisplayPanel 各自维护独立当前对象状态。',
60
60
  '【右侧信息栏整体拖拽】在客服工作台框架模版里,右侧 InfoDisplayPanel 必须支持整体宽度拖拽,拖拽热区位于信息栏左边界,默认热区 8px;拖拽时改变的是右侧信息栏整体宽度,左侧 IM 聊天区自动占满剩余空间,而不是改由 InfoDisplayPanel 本体处理页面级宽度。推荐默认宽度约 380px,最小宽度 320px,并保证左侧 IM 区至少保留 360px 可读宽度;同时保留 InfoDisplayPanel 内部相邻栏之间的原生拖拽能力。',
61
61
  '【InfoDisplayPanel 动态 tab 拆分】右侧信息展示区必须保留 InfoDisplayPanel 的动态 tab1 / tab2 / tab3 栏拆分能力:业务只有 1 个分类时降级为单栏标题 + 内容;业务有 2 个分类时最多支持拆成 2 栏;业务有 3 个及以上分类且宽度达到三栏门槛时支持拆成 3 栏。tab 数量、名称、顺序和内容必须来自当前会话 / 工单上下文的业务数据,不允许固定写死 3 个静态按钮,也不允许用多个 Card 或 div 手搓三栏替代。',
62
+ '【右侧信息 Tab 区内容定义】客服工作台右侧 InfoDisplayPanel 是“当前处理对象的辅助信息工作区”,每个 tab 都是业务内容插槽而不是固定占位。业务可在 `panels[].content` 中直接传入任意 React 内容,或通过 `renderPanelContent({ panel, index, activeTabId })` 按当前 tab 动态渲染;允许承载托管助手 ChatMessage 执行流、用户画像、订单/商品详情、历史工单、工单日志、沟通记录、风险提示、售后策略、处理工具、表单、表格片段、图文/视频信息或自定义复合组件。',
63
+ '【右侧信息 Tab 区自定义约束】自定义内容只能替换每个 tab 的内容区,不能替换 InfoDisplayPanel 的外框、Tabs、拆分/合并按钮、栏间拖拽、最小宽度、响应式列数和滚动容器。内容组件必须使用 `w-full min-w-0`、自动换行和内部滚动适配窄栏;禁止写固定大宽度、绝对定位撑破栏位,禁止为了展示自定义内容而改用多个 Card、自制 Tabs 或普通 div 拼出右侧信息栏。',
62
64
  '【拖拽与拆分保留验收】客服工作台生成页必须同时满足:拖动左侧列表边界时 ConversationList 默认列表 / 卡片列表容器实时适配;拖动右侧 InfoDisplayPanel 左边界时信息栏整体宽度变化且 IM 区自动吃剩余空间;拆出 InfoDisplayPanel tab 后,栏间分隔线可继续调节相邻栏宽,且每栏不小于 200px。任一能力缺失都视为没有正确使用客服工作台框架。',
63
65
  '【右侧插槽】如业务需要替换右侧主内容,可放 ChatConversationPattern、Table、表单详情或工单处理面板;但客服接待类页面优先保持“IM 对话 + InfoDisplayPanel”的组合,不要用多个普通 Card 临时拼出右侧信息区。',
64
66
  '【AI 选型】当 prompt 出现“客服工作台框架 / 客服工作台 / 客服在线工作台 / 在线客服工作台 / 在线 Agent / Agent 工作台 / 基础模式 / 托管模式 / 客服名称在线状态 / 顶部指标工具条”等客服工作台信号时,优先选本页面模板;但本模板右侧主内容仍必须包含 IMConversationPattern 聊天区和 InfoDisplayPanel 信息区。如果只是全局左侧导航,选 NavBar;如果只是会话队列,选 ConversationList;如果只是单条消息,选 ChatMessage / ChatBubble。',
@@ -207,11 +209,13 @@ export default function MyPage({ defaultSelectedItemId = 'example-1' }) {
207
209
  rules: [
208
210
  '【五模板定位】本模板只用于“从 0 发起 AI 任务”的入口页:用户还没进入某个对象、会话或产物,第一步是输入一句需求、搜索助手、选择模板/案例/推荐 Agent。若用户要管理一批助手/模板/机器人列表,用 `BasePageFramePattern`;若用户已经在任务内持续追问,用 `ChatConversationPattern`;若右侧有主文档/主画布/主配置,AI 只是辅助,用 `CopilotPagePattern`;若是客服/私信线程,用 `IMConversationPattern`。',
209
211
  '【布局 Recipe】必须按 `LAYOUT_RECIPES.md` 选择 `chat-home`:右侧主内容区是 gray-direct,不是白色 `WorkSurface`;Hero / ChatInput / Tabs / 搜索 / 模板卡网格直接坐 `var(--color-blueGrey-200)`。',
210
- '【组件选型门禁】主输入必须用 `ChatInput`,不可用原生 textarea 或 Input 冒充 AI 输入;模板/助手/案例推荐必须用 `Card`;分类切换必须用 `Tabs`;搜索框用 `Input` + `Icon` 前缀;无结果用 `Empty`。禁止用 Button/Tag 排一行冒充 Tabs,禁止手写卡片。',
212
+ '【组件选型门禁】主输入必须用 `ChatInput`,不可用原生 textarea、InputForm 字段冒充 AI 输入;模板/助手/案例推荐必须用 `Card`;分类切换必须用 `Tabs`;搜索框用 `Input` + `Icon` 前缀;无结果用 `Empty`。禁止用 Button/Tag 排一行冒充 Tabs,禁止手写卡片。',
211
213
  '【整体背景】页面以浅灰大背景(`--color-blueGrey-200`)为主:Hero、筛选行、卡片网格**直接坐灰底**,不要再额外包一整张“右侧白色大卡片容器”(白卡只用于推荐卡片本身)。',
212
214
  '【反例扫描】如果同一右侧首页函数里同时出现 `ChatInput`、模板 `Card` 网格,并且外层存在 `background: var(--color-surface)` / `bg-surface rounded-*` 包住 Hero + 输入框 + Tabs + 网格,说明 AI 把入口页误生成成白卡管理页,必须拆掉这层 wrapper。',
213
215
  '【框架不动】外框灰底 + 左侧 `NavBar` 固定不变;右侧内容区必须 `flex-1 min-w-0 min-h-0 overflow-y-auto overflow-x-hidden`,由右侧内容区作为唯一滚动容器承载整体页面滚动;Hero、ChatInput、筛选行、卡片网格要一起被滑动,禁止只让下方卡片列表单独 `overflow-y-auto`。',
214
216
  '【Hero 区】居中欢迎标题 + 最大宽 680px 的 ChatInput 直接坐浅灰底;Hero 主标题使用 `text-4xl leading-10`;默认使用 `ChatInput variant="default"` 作为入口主输入;标准节奏为 `padding: 84px 40px 80px`,标题区较旧版整体上移 36px。标题区副标题与 ChatInput 之间必须保留 48px 间距(比普通标题说明节奏额外增加 24px),保证输入框不贴近标题区。AI 渐变只用于输入框内部 / AI 标识,不作为整块 Hero 白底。',
217
+ '【Hero 输入区禁止 Form】Hero 区的标题、副标题、AI 身份提示和主 ChatInput 都属于入口页居中启动区,不是表单字段。禁止用 `<Form />`、Form 的 `items[].label`、`labelAi` 或字段标题来包裹 / 模拟输入框顶部的“AI”标题;如果需要在输入框上方展示“AI / 助手名 / 问候语”,必须作为 Hero 内容用普通居中布局实现,并保持 `text-center`、`items-center`、`maxWidth: 680px` 的 ChatInput 容器。',
218
+ '【Hero 反例扫描】同一 ChatHome 页面中如果出现 `<Form items={[{ label: "AI", type: "input" | "textarea" }]}>`、`labelAi` 或 Form 字段标题紧贴主 ChatInput 上方,说明 AI 把入口页主输入误生成为表单字段,必须删除 Form,改回居中 Hero 标题 + `<ChatInput variant="default" />`。',
215
219
  '【筛选与分类】分类切换必须用 `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 下方单独占一行。',
216
220
  '【卡片列表】使用自适应 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` 造成只有卡片区滚动。',
217
221
  '【内容可扩展】Tab 项数量和卡片数据替换为业务真实数据;卡片 stats/tags 按业务维度自定义',
@@ -452,6 +452,27 @@ const TEXTAREA_SINGLE_SUGGESTIONS = [
452
452
  '可先生成一版客服备注草稿,后续结合人工判断再做补充和修订。',
453
453
  ];
454
454
 
455
+ const TEXTAREA_CODE_SINGLE_SUGGESTIONS = [
456
+ 'export async function handleUserQuery(input) {\n const intent = await detectIntent(input);\n return generateReply(intent);\n}',
457
+ 'if (user.role === "admin") {\n return next();\n}\nreturn res.status(403).send("Forbidden");',
458
+ 'const config = {\n model: "tf-base-v2",\n temperature: 0.3,\n maxTokens: 1024,\n};',
459
+ ];
460
+
461
+ const TEXTAREA_CODE_MULTI_SUGGESTION_GROUPS = [
462
+ [
463
+ 'const reply = await callModel(prompt, { temperature: 0.3 });',
464
+ 'logger.info("[chat] reply.length=", reply.length);',
465
+ ],
466
+ [
467
+ 'try {\n await retry(fetchKnowledge, 3);\n} catch (err) {\n reportError(err);\n}',
468
+ 'return formatResponse({ status: "ok", data });',
469
+ ],
470
+ [
471
+ '// 命中知识库后再调用大模型,避免无依据生成',
472
+ 'const hits = await searchKnowledge(query);\nconst answer = await llm.complete({ context: hits, query });',
473
+ ],
474
+ ];
475
+
455
476
  const TEXTAREA_MULTI_SUGGESTION_GROUPS = [
456
477
  [
457
478
  '根据当前对话内容,建议先记录用户诉求、异常现象和下一步处理动作。',
@@ -482,7 +503,7 @@ function TextAreaPreview({
482
503
  }) {
483
504
  const isCodeVariant = variant === 'code';
484
505
  const sample = isCodeVariant
485
- ? '# 角色\n你是抖音安全中心的小安智能助手,负责理解用户诉求并给出清晰答案。\n\n# 注意\n1. 回答前先判断是否需要调用知识库。\n2. 需要给出可执行、可验证的处理建议。'
506
+ ? 'import { createServer } from "node:http";\n\nconst server = createServer(async (req, res) => {\n if (req.url === "/health") {\n res.writeHead(200, { "Content-Type": "application/json" });\n res.end(JSON.stringify({ status: "ok" }));\n return;\n }\n res.writeHead(404);\n res.end("Not Found");\n});\n\nserver.listen(3000, () => {\n console.log("server listening on :3000");\n});'
486
507
  : '我们非常高兴地向您介绍我们的最新产品 HiUI。HiUI是一个创新的在线客服解决方案,旨在帮助企业更好地理解并服务他们的客户。';
487
508
  const defaultValue = fillMode === 'filled' ? sample : '';
488
509
  const enforceMaxLength = !countOverflow;
@@ -493,16 +514,18 @@ function TextAreaPreview({
493
514
  const [multiIndex, setMultiIndex] = useState(0);
494
515
  const hasSingleSuggestion = typeof aiSuggestion === 'string' && aiSuggestion.trim().length > 0;
495
516
  const hasMultiSuggestions = Array.isArray(aiSuggestions) && aiSuggestions.length > 0;
517
+ const singlePool = isCodeVariant ? TEXTAREA_CODE_SINGLE_SUGGESTIONS : TEXTAREA_SINGLE_SUGGESTIONS;
518
+ const multiPool = isCodeVariant ? TEXTAREA_CODE_MULTI_SUGGESTION_GROUPS : TEXTAREA_MULTI_SUGGESTION_GROUPS;
496
519
 
497
520
  const handleRefresh = useCallback(() => {
498
521
  if (hasMultiSuggestions) {
499
- setMultiIndex((prev) => (prev + 1) % TEXTAREA_MULTI_SUGGESTION_GROUPS.length);
522
+ setMultiIndex((prev) => (prev + 1) % multiPool.length);
500
523
  return;
501
524
  }
502
525
  if (hasSingleSuggestion) {
503
- setSingleIndex((prev) => (prev + 1) % TEXTAREA_SINGLE_SUGGESTIONS.length);
526
+ setSingleIndex((prev) => (prev + 1) % singlePool.length);
504
527
  }
505
- }, [hasMultiSuggestions, hasSingleSuggestion]);
528
+ }, [hasMultiSuggestions, hasSingleSuggestion, multiPool.length, singlePool.length]);
506
529
 
507
530
  return (
508
531
  <FormControlPreviewWidth>
@@ -518,8 +541,8 @@ function TextAreaPreview({
518
541
  {...(showCountDemo ? { maxLength: 140, enforceMaxLength } : {})}
519
542
  resize={resize}
520
543
  fillHeight={fillHeight}
521
- aiSuggestion={hasSingleSuggestion ? TEXTAREA_SINGLE_SUGGESTIONS[singleIndex] : undefined}
522
- aiSuggestions={hasMultiSuggestions ? TEXTAREA_MULTI_SUGGESTION_GROUPS[multiIndex] : undefined}
544
+ aiSuggestion={hasSingleSuggestion ? singlePool[singleIndex] : undefined}
545
+ aiSuggestions={hasMultiSuggestions ? multiPool[multiIndex] : undefined}
523
546
  onRefreshAiSuggestions={hasSingleSuggestion || hasMultiSuggestions ? handleRefresh : undefined}
524
547
  className={fillHeight ? 'flex-1 min-h-0 w-full' : ''}
525
548
  />
@@ -842,11 +865,30 @@ const FORM_FIELD_ATOM_ENUM_PROPS = {
842
865
 
843
866
  const FORM_TEXTAREA_SAMPLE =
844
867
  '我们非常高兴地向您介绍我们的最新产品 HiUI。HiUI是一个创新的在线客服解决方案,旨在帮助企业更好地理解并服务他们的客户。';
868
+ const FORM_AI_SUGGESTION_FIELD_TYPES = ['input', 'textarea', 'select'];
869
+ const FORM_AI_SUGGESTIONS = {
870
+ input: ['您好,正在为您核实,请稍候。', '抱歉给您带来不便,这边已经帮您加急处理。'],
871
+ textarea: ['建议优先整理用户诉求、处理时效和需补充的材料信息。', '如果需要后续交接,可一并记录当前判责结论和预计回访时间。'],
872
+ select: ['选项 A', '选项 B'],
873
+ };
845
874
 
846
875
  function getFormFieldAtomEnumProps(fieldType) {
847
876
  return FORM_FIELD_ATOM_ENUM_PROPS[fieldType] || [];
848
877
  }
849
878
 
879
+ function supportsFormAiSuggestion(fieldType) {
880
+ return FORM_AI_SUGGESTION_FIELD_TYPES.includes(fieldType);
881
+ }
882
+
883
+ function buildFormAiSuggestionProps(fieldType, enabled) {
884
+ if (!enabled || !supportsFormAiSuggestion(fieldType)) return {};
885
+ return {
886
+ aiSuggestions: FORM_AI_SUGGESTIONS[fieldType],
887
+ onRefreshAiSuggestions: () => {},
888
+ onAdoptSuggestion: () => {},
889
+ };
890
+ }
891
+
850
892
  function buildFormFieldAtomProps(fieldType, enums, controlValues = {}) {
851
893
  if (fieldType === 'radio' || fieldType === 'checkbox') {
852
894
  return {
@@ -963,6 +1005,7 @@ function FormPreview({
963
1005
  labelRequired,
964
1006
  labelAi,
965
1007
  labelHelp,
1008
+ formAiSuggestion,
966
1009
  fieldAtomProps,
967
1010
  }) {
968
1011
  const baseItems = buildFormPreviewItems({
@@ -994,7 +1037,7 @@ function FormPreview({
994
1037
 
995
1038
  const preview = (
996
1039
  <Form
997
- key={`form-${previewMode}-${fieldType}-${labelPosition}-${helpMode}-${validationState}-${size}-${labelOptional}-${labelRequired}-${labelAi}-${labelHelp}-${JSON.stringify(fieldAtomProps || {})}`}
1040
+ key={`form-${previewMode}-${fieldType}-${labelPosition}-${helpMode}-${validationState}-${size}-${labelOptional}-${labelRequired}-${labelAi}-${labelHelp}-${formAiSuggestion}-${JSON.stringify(fieldAtomProps || {})}`}
998
1041
  layout="vertical"
999
1042
  columns={1}
1000
1043
  labelPosition={labelPosition}
@@ -2737,9 +2780,18 @@ export const PREVIEW_REGISTRY = {
2737
2780
  component: FormPreview,
2738
2781
  tokenMap: FORM_TOKEN_MAP,
2739
2782
 
2740
- normalizeControlValues: ({ changedControlId, nextControlValues, prevControlValues }) => (
2741
- normalizeSliderPreviewControls(nextControlValues, prevControlValues, changedControlId, 'slider')
2742
- ),
2783
+ normalizeControlValues: ({ changedControlId, nextControlValues, prevControlValues }) => {
2784
+ const normalized = normalizeSliderPreviewControls(nextControlValues, prevControlValues, changedControlId, 'slider');
2785
+ const previewMode = normalized.previewMode || 'single';
2786
+ const fieldType = normalized.fieldType || 'input';
2787
+ const supportsAi = previewMode === 'single' && supportsFormAiSuggestion(fieldType);
2788
+ const formAiSuggestion = supportsAi ? (normalized.formAiSuggestion || 'off') : 'off';
2789
+ return {
2790
+ ...normalized,
2791
+ formAiSuggestion,
2792
+ labelAi: formAiSuggestion === 'on' ? true : normalized.labelAi,
2793
+ };
2794
+ },
2743
2795
 
2744
2796
  getPreviewAreaStyle: ({ controlValues }) => ({
2745
2797
  alignItems: (controlValues.previewMode || 'single') === 'all' ? 'flex-start' : 'center',
@@ -2784,6 +2836,20 @@ export const PREVIEW_REGISTRY = {
2784
2836
  default: 'input',
2785
2837
  hidden: ({ controlValues }) => (controlValues.previewMode || 'single') === 'all',
2786
2838
  },
2839
+ {
2840
+ id: 'formAiSuggestion',
2841
+ label: 'AI推荐',
2842
+ type: 'seg',
2843
+ options: [
2844
+ { id: 'off', label: '隐藏' },
2845
+ { id: 'on', label: '显示' },
2846
+ ],
2847
+ default: 'off',
2848
+ hidden: ({ controlValues }) => (
2849
+ (controlValues.previewMode || 'single') === 'all'
2850
+ || !supportsFormAiSuggestion(controlValues.fieldType || 'input')
2851
+ ),
2852
+ },
2787
2853
  {
2788
2854
  id: 'labelPosition',
2789
2855
  label: '标签位置',
@@ -2871,9 +2937,13 @@ export const PREVIEW_REGISTRY = {
2871
2937
  size: 'md',
2872
2938
  labelOptional: cv.labelOptional === true,
2873
2939
  labelRequired: cv.labelRequired === true,
2874
- labelAi: cv.labelAi === true,
2940
+ labelAi: cv.labelAi === true || (cv.formAiSuggestion === 'on' && supportsFormAiSuggestion(cv.fieldType || 'input')),
2875
2941
  labelHelp: cv.labelHelp === true,
2876
- fieldAtomProps: buildFormFieldAtomProps(cv.fieldType || 'input', enums, cv),
2942
+ formAiSuggestion: cv.formAiSuggestion || 'off',
2943
+ fieldAtomProps: {
2944
+ ...buildFormFieldAtomProps(cv.fieldType || 'input', enums, cv),
2945
+ ...buildFormAiSuggestionProps(cv.fieldType || 'input', cv.formAiSuggestion === 'on'),
2946
+ },
2877
2947
  }),
2878
2948
 
2879
2949
  generateUsage: (enums, cv) => {
@@ -2885,9 +2955,13 @@ export const PREVIEW_REGISTRY = {
2885
2955
  const validationState = cv.validationState || 'default';
2886
2956
  const labelOptional = cv.labelOptional === true;
2887
2957
  const labelRequired = cv.labelRequired === true;
2888
- const labelAi = cv.labelAi === true;
2958
+ const labelAi = cv.labelAi === true || (cv.formAiSuggestion === 'on' && supportsFormAiSuggestion(fieldType));
2889
2959
  const labelHelp = cv.labelHelp === true;
2890
- const fieldAtomProps = buildFormFieldAtomProps(fieldType, enums, cv);
2960
+ const showAiSuggestion = cv.formAiSuggestion === 'on' && supportsFormAiSuggestion(fieldType);
2961
+ const fieldAtomProps = {
2962
+ ...buildFormFieldAtomProps(fieldType, enums, cv),
2963
+ ...buildFormAiSuggestionProps(fieldType, showAiSuggestion),
2964
+ };
2891
2965
 
2892
2966
  if (previewMode === 'all') {
2893
2967
  lines.push('');
@@ -2947,6 +3021,11 @@ export const PREVIEW_REGISTRY = {
2947
3021
  lines.push(' onChange: (value) => {},');
2948
3022
  lines.push(' onAfterChange: (value) => {},');
2949
3023
  }
3024
+ if (showAiSuggestion) {
3025
+ lines.push(` aiSuggestions: ${formatUsageLiteral(FORM_AI_SUGGESTIONS[fieldType])},`);
3026
+ lines.push(' onRefreshAiSuggestions: () => {},');
3027
+ lines.push(' onAdoptSuggestion: (value) => {},');
3028
+ }
2950
3029
  lines.push('}];');
2951
3030
  lines.push('');
2952
3031
  lines.push('<Form');
package/src/index.d.ts CHANGED
@@ -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,并按业务动态 tab1 / tab2 / tab3 拆分为 1-3 栏,支持整体宽度与栏间宽度拖拽。 */
264
+ /** InfoDisplayPanel — 信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并按业务动态 tab1 / tab2 / tab3 拆分为 1-3 栏,支持整体宽度与栏间宽度拖拽,且每个 tab 内容都可由业务完全自定义。 */
265
265
  export interface InfoDisplayPanelProps extends TfdsCommonProps {
266
266
  /** array, default: null */
267
267
  panels?: unknown[];
@@ -472,7 +472,7 @@ export interface FormTitleProps extends TfdsCommonProps {
472
472
  }
473
473
  export const FormTitle: React.FC<FormTitleProps>;
474
474
 
475
- /** Form — B端表单组合组件,负责字段标题、说明、错误反馈与 top/left 布局;字段控件复用平台已有基础组件,覆盖 13 类表单字段。 */
475
+ /** Form — B端表单组合组件,本质是字段标题 / 说明 / 错误反馈与各类表单基础组件的组合层;字段控件完整复用对应基础组件的默认能力、交互、状态、尺寸和 AI 推荐能力。 */
476
476
  export interface FormProps extends TfdsCommonProps {
477
477
  /** enum<vertical | grid>, default: "vertical" */
478
478
  layout?: "vertical" | "grid";