From 431b1f2b1d50b9d2920484a83c581af4a66ac6eb Mon Sep 17 00:00:00 2001 From: wanghanlin <1533525126@qq.com> Date: Wed, 19 Aug 2026 17:45:07 +0800 Subject: [PATCH] =?UTF-8?q?fix(client):=20=E4=BF=AE=E5=A4=8D=E7=AC=AC?= =?UTF-8?q?=E4=B8=89=E6=96=B9=E6=8E=A5=E5=85=A5=20SDK=20=E5=90=8E=20markdo?= =?UTF-8?q?wn=20=E6=8E=92=E7=89=88=E4=B8=8E=E6=A0=87=E7=AD=BE=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E7=BC=BA=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:tdesign-web-components 的聊天/markdown 变量(--td-chat-*/--td-chat-md-*/--td-chat-input-*) 定义在 globalCSS 的 constructable stylesheet 中,选择器为 :root;但 adoptedStyleSheets 里的 :root 不匹配 shadow root,导致这些变量无法进入渲染 markdown 的 shadow DOM,标题/列表/引用/ 代码块回退为初始值。t-tag 是 light DOM 组件,其 .t-tag 样式同样只在 globalCSS 中、进不了 light DOM。 改动: - styles.ts 新增 tdChatVars(),将聊天/markdown/input 变量自包含声明在 .csk-root 上 - styles.ts 补注入 .csk-root .t-tag 系列 light DOM 样式 - 配合此前 tdThemeVars() 的主题变量,SDK 样式完全自包含,不再依赖宿主页面 --- client/CLAUDE.md | 2 + client/src/styles.ts | 666 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 654 insertions(+), 14 deletions(-) diff --git a/client/CLAUDE.md b/client/CLAUDE.md index ded1e16..45fd222 100644 --- a/client/CLAUDE.md +++ b/client/CLAUDE.md @@ -68,6 +68,8 @@ init(rawConfig) - **`api.ts`**:HTTP 封装 + SSE 流解析。三种 SSE 接口(普通流式、RAG 流式、同步)的解析逻辑都在这里,注意流式解析的边界处理。 - **`dom.ts`**:纯 DOM 构建与事件绑定,含知识库下拉、RAG 来源卡片、历史会话面板的渲染。 - **`styles.ts`**:所有 CSS 字符串模板,按 `primaryColor` 动态着色,并通过 `--td-*` CSS 变量透传给 TDesign 组件。改动 UI 视觉改这里,**不要**在 `dom.ts` 里写内联样式。 + - **主题/聊天变量自包含约定**:TDesign 的变量分为两类,都必须自包含声明在 `.csk-root` 上(`tdThemeVars()` 负责主题变量 `--td-brand/--td-gray/--td-bg/...`,`tdChatVars()` 负责聊天/markdown/input 变量 `--td-chat-*`/`--td-chat-md-*`/`--td-chat-input-*`)。原因:这些变量在 tdesign-web-components 里定义于 `globalCSS` 的 constructable stylesheet、选择器为 `:root`,但 `adoptedStyleSheets` 里的 `:root` 不匹配 shadow root,无法进入 shadow DOM;直接声明在 `.csk-root` 上才能经 CSS 自定义属性继承穿透 shadow DOM。升级 `tdesign-web-components` 版本时需同步核对 `style/index.js` 的 `css$2`/`css`/`css$1` 三个变量块。 + - **light-DOM 组件样式补注入**:`t-tag` 是 tdesign-web-components 的 light DOM 组件(`isLightDOM: true`),其 `.t-tag` 样式在 `globalCSS` 里只对 shadow DOM 生效、进不了 light DOM,需在 `getStyles()` 里以 `.csk-root .t-tag` 前缀手动补注入(来源 `_chunks/dep-5245073d.js`)。新增 light DOM 组件时同样排查是否需要补样式。 - **`markdown.ts`**:已废弃并移除。原基于 marked 的 Markdown 渲染器改由 TDesign Chat 组件(`t-chat-item`)内置 cherry-markdown 承担,无需额外加载 `marked.min.js`。cherry-markdown 的 XSS 防护由 TDesign 组件保证;如后续需要自定义渲染,仍需维持白名单/转义等安全策略。 ### 单例与生命周期 diff --git a/client/src/styles.ts b/client/src/styles.ts index f37fd28..051451a 100644 --- a/client/src/styles.ts +++ b/client/src/styles.ts @@ -15,6 +15,39 @@ function hexToRgb(hex: string): string { return `${parseInt(match[1], 16)}, ${parseInt(match[2], 16)}, ${parseInt(match[3], 16)}`; } +/** 两个 #RRGGBB 颜色之间的 RGB 线性插值;ratio∈[0,1],0=from,1=to */ +function mixColor(from: string, to: string, ratio: number): string { + const parse = (hex: string): [number, number, number] | null => { + const m = hex.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/); + if (!m) return null; + return [parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16)]; + }; + const a = parse(from); + const b = parse(to); + if (!a || !b) return from; + const mix = (x: number, y: number) => Math.round(x + (y - x) * ratio); + const r = mix(a[0], b[0]); + const g = mix(a[1], b[1]); + const bl = mix(a[2], b[2]); + return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${bl.toString(16).padStart(2, '0')}`; +} + +/** TDesign 默认蓝阶梯(primaryColor 非 #RRGGBB 时的回退值,对应 --td-brand-color-1..10) */ +const DEFAULT_BRAND_LADDER = ['#f2f3ff', '#d9e1ff', '#b5c7ff', '#8eabff', '#618dff', '#366ef4', '#0052d9', '#003cab', '#002a7c', '#001a57']; + +/** 由 primaryColor 生成 10 级品牌色阶梯,index 6(即 --td-brand-color-7)为 primaryColor 本身 */ +function brandLadder(hex: string): string[] { + if (!/^#[0-9a-fA-F]{6}$/.test(hex)) return DEFAULT_BRAND_LADDER; + // 浅侧(-1..-6)向白插值,深侧(-8..-10)向黑插值 + const lightRatios = [0.96, 0.78, 0.60, 0.42, 0.28, 0.15]; + const darkRatios = [0.18, 0.40, 0.62]; + const ladder: string[] = []; + for (const r of lightRatios) ladder.push(mixColor(hex, '#ffffff', r)); + ladder.push(hex); + for (const r of darkRatios) ladder.push(mixColor(hex, '#000000', r)); + return ladder; +} + /** CSS 变量:将配置中的主题色转换为 CSS 自定义属性 */ function cssVars(config: ResolvedConfig): string { const darker = adjustColor(config.primaryColor, -15); @@ -40,20 +73,6 @@ function cssVars(config: ResolvedConfig): string { --csk-text-secondary: rgba(0, 0, 0, 0.6); --csk-text-muted: rgba(0, 0, 0, 0.4); --csk-bg-app: #FFFFFF; - /* TDesign 组件品牌色覆盖:让 primaryColor 透传到 t-chat-item/t-chat-sender/t-tag 等 */ - /* CSS 变量会穿透 shadow DOM,故在 .csk-root 上定义即可被内部组件继承 */ - --td-brand-color: ${config.primaryColor}; - --td-brand-color-hover: ${darker}; - --td-brand-color-active: ${darker}; - --td-brand-color-focus: ${config.primaryColor}; - --td-brand-color-disabled: ${lighter}; - --td-brand-color-light: ${lighter}; - --td-brand-color-light-hover: ${lighter}; - /* 覆盖 TDesign chat-item 默认 800px 限制,使 AI 回复内容自适应 SDK 弹窗宽度 */ - --td-chat-item-content-max-width: 100%; - /* 压缩输入组件:缩小内容区上下 padding 与字号(变量穿透 shadow DOM 生效于 t-chat-sender) */ - --td-chat-input-padding: 8px 12px; - --td-chat-input-font-size: 14px; `; } @@ -70,11 +89,524 @@ function adjustColor(hex: string, amount: number): string { return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; } +/** + * TDesign light 主题变量:在 .csk-root 上自包含声明,使 SDK 的 shadow DOM 组件 + * (t-chat-item / t-button / t-tag / t-select / t-chat-action / t-chat-sender)不依赖 + * 宿主页面的 :root --td-* 变量。 + * + * 原理:CSS 自定义属性直接声明在元素上永远压过继承值(与选择器特异性/顺序无关), + * 故把整套 light 主题变量直接写在 .csk-root 上即可免疫宿主对 :root 的覆盖。 + * + * 内容对应 tdesign-web-components v1.2.10 的 style/index.js 中 css$2 的 light 块 + * (:root, :root[theme-mode="light"] 块 + :root 的 radius/font/size/comp 块)。 + * 品牌色阶梯由 primaryColor 动态派生;升级 tdesign-web-components 版本时需同步核对。 + */ +function tdThemeVars(config: ResolvedConfig): string { + const darker = adjustColor(config.primaryColor, -15); + const lighter = adjustColor(config.primaryColor, 18); + const [b1, b2, b3, b4, b5, b6, b7, b8, b9, b10] = brandLadder(config.primaryColor); + + return ` + /* ===== 品牌色阶梯(跟随 primaryColor) ===== */ + --td-brand-color-1: ${b1}; + --td-brand-color-2: ${b2}; + --td-brand-color-3: ${b3}; + --td-brand-color-4: ${b4}; + --td-brand-color-5: ${b5}; + --td-brand-color-6: ${b6}; + --td-brand-color-7: ${b7}; + --td-brand-color-8: ${b8}; + --td-brand-color-9: ${b9}; + --td-brand-color-10: ${b10}; + + /* ===== 警告 / 错误 / 成功阶梯(不跟随主题色) ===== */ + --td-warning-color-1: #fff1e9; + --td-warning-color-2: #ffd9c2; + --td-warning-color-3: #ffb98c; + --td-warning-color-4: #fa9550; + --td-warning-color-5: #e37318; + --td-warning-color-6: #be5a00; + --td-warning-color-7: #954500; + --td-warning-color-8: #713300; + --td-warning-color-9: #532300; + --td-warning-color-10: #3b1700; + --td-error-color-1: #fff0ed; + --td-error-color-2: #ffd8d2; + --td-error-color-3: #ffb9b0; + --td-error-color-4: #ff9285; + --td-error-color-5: #f6685d; + --td-error-color-6: #d54941; + --td-error-color-7: #ad352f; + --td-error-color-8: #881f1c; + --td-error-color-9: #68070a; + --td-error-color-10: #490002; + --td-success-color-1: #e3f9e9; + --td-success-color-2: #c6f3d7; + --td-success-color-3: #92dab2; + --td-success-color-4: #56c08d; + --td-success-color-5: #2ba471; + --td-success-color-6: #008858; + --td-success-color-7: #006c45; + --td-success-color-8: #005334; + --td-success-color-9: #003b23; + --td-success-color-10: #002515; + + /* ===== 灰阶 ===== */ + --td-gray-color-1: #f3f3f3; + --td-gray-color-2: #eee; + --td-gray-color-3: #e8e8e8; + --td-gray-color-4: #ddd; + --td-gray-color-5: #c6c6c6; + --td-gray-color-6: #a6a6a6; + --td-gray-color-7: #8b8b8b; + --td-gray-color-8: #777; + --td-gray-color-9: #5e5e5e; + --td-gray-color-10: #4b4b4b; + --td-gray-color-11: #393939; + --td-gray-color-12: #2c2c2c; + --td-gray-color-13: #242424; + --td-gray-color-14: #181818; + + /* ===== 白 / 灰字体色 ===== */ + --td-font-white-1: #ffffff; + --td-font-white-2: rgba(255, 255, 255, 0.55); + --td-font-white-3: rgba(255, 255, 255, 0.35); + --td-font-white-4: rgba(255, 255, 255, 0.22); + --td-font-gray-1: rgba(0, 0, 0, 0.9); + --td-font-gray-2: rgba(0, 0, 0, 0.6); + --td-font-gray-3: rgba(0, 0, 0, 0.4); + --td-font-gray-4: rgba(0, 0, 0, 0.26); + + /* ===== 品牌语义色(沿用 SDK 现有映射,hover/active 加深、light 变浅) ===== */ + --td-brand-color: ${config.primaryColor}; + --td-brand-color-hover: ${darker}; + --td-brand-color-active: ${darker}; + --td-brand-color-focus: ${config.primaryColor}; + --td-brand-color-disabled: ${lighter}; + --td-brand-color-light: ${lighter}; + --td-brand-color-light-hover: ${lighter}; + /* ===== 警告 / 错误 / 成功 语义色 ===== */ + --td-warning-color: var(--td-warning-color-5); + --td-error-color: var(--td-error-color-6); + --td-success-color: var(--td-success-color-5); + --td-warning-color-hover: var(--td-warning-color-4); + --td-warning-color-focus: var(--td-warning-color-2); + --td-warning-color-active: var(--td-warning-color-6); + --td-warning-color-disabled: var(--td-warning-color-3); + --td-warning-color-light: var(--td-warning-color-1); + --td-warning-color-light-hover: var(--td-warning-color-2); + --td-error-color-hover: var(--td-error-color-5); + --td-error-color-focus: var(--td-error-color-2); + --td-error-color-active: var(--td-error-color-7); + --td-error-color-disabled: var(--td-error-color-3); + --td-error-color-light: var(--td-error-color-1); + --td-error-color-light-hover: var(--td-error-color-2); + --td-success-color-hover: var(--td-success-color-4); + --td-success-color-focus: var(--td-success-color-2); + --td-success-color-active: var(--td-success-color-6); + --td-success-color-disabled: var(--td-success-color-3); + --td-success-color-light: var(--td-success-color-1); + --td-success-color-light-hover: var(--td-success-color-2); + + /* ===== 遮罩 / 背景 / 文字 / 边框 / 阴影 / 滚动条 ===== */ + --td-mask-active: rgba(0, 0, 0, 0.6); + --td-mask-disabled: rgba(255, 255, 255, 0.6); + --td-bg-color-page: var(--td-gray-color-2); + --td-bg-color-container: #fff; + --td-bg-color-container-hover: var(--td-gray-color-1); + --td-bg-color-container-active: var(--td-gray-color-3); + --td-bg-color-container-select: #fff; + --td-bg-color-secondarycontainer: var(--td-gray-color-1); + --td-bg-color-secondarycontainer-hover: var(--td-gray-color-2); + --td-bg-color-secondarycontainer-active: var(--td-gray-color-4); + --td-bg-color-component: var(--td-gray-color-3); + --td-bg-color-component-hover: var(--td-gray-color-4); + --td-bg-color-component-active: var(--td-gray-color-6); + --td-bg-color-secondarycomponent: var(--td-gray-color-4); + --td-bg-color-secondarycomponent-hover: var(--td-gray-color-5); + --td-bg-color-secondarycomponent-active: var(--td-gray-color-6); + --td-bg-color-component-disabled: var(--td-gray-color-2); + --td-bg-color-specialcomponent: #fff; + --td-text-color-primary: var(--td-font-gray-1); + --td-text-color-secondary: var(--td-font-gray-2); + --td-text-color-placeholder: var(--td-font-gray-3); + --td-text-color-disabled: var(--td-font-gray-4); + --td-text-color-anti: #fff; + --td-text-color-brand: var(--td-brand-color-7); + --td-text-color-link: var(--td-brand-color-8); + --td-border-level-1-color: var(--td-gray-color-3); + --td-component-stroke: var(--td-gray-color-3); + --td-border-level-2-color: var(--td-gray-color-4); + --td-component-border: var(--td-gray-color-4); + --td-shadow-1: 0 1px 10px rgba(0, 0, 0, 0.05), 0 4px 5px rgba(0, 0, 0, 0.08), 0 2px 4px -1px rgba(0, 0, 0, 0.12); + --td-shadow-2: 0 3px 14px 2px rgba(0, 0, 0, 0.05), 0 8px 10px 1px rgba(0, 0, 0, 0.06), 0 5px 5px -3px rgba(0, 0, 0, 0.1); + --td-shadow-3: 0 6px 30px 5px rgba(0, 0, 0, 0.05), 0 16px 24px 2px rgba(0, 0, 0, 0.04), 0 8px 10px -5px rgba(0, 0, 0, 0.08); + --td-shadow-inset-top: inset 0 0.5px 0 #dcdcdc; + --td-shadow-inset-right: inset 0.5px 0 0 #dcdcdc; + --td-shadow-inset-bottom: inset 0 -0.5px 0 #dcdcdc; + --td-shadow-inset-left: inset -0.5px 0 0 #dcdcdc; + --td-table-shadow-color: rgba(0, 0, 0, 0.08); + --td-scrollbar-color: rgba(0, 0, 0, 0.1); + --td-scrollbar-hover-color: rgba(0, 0, 0, 0.3); + --td-scroll-track-color: #fff; + + /* ===== 圆角 ===== */ + --td-radius-small: 2px; + --td-radius-default: 3px; + --td-radius-medium: 6px; + --td-radius-large: 9px; + --td-radius-extraLarge: 12px; + --td-radius-round: 999px; + --td-radius-circle: 50%; + + /* ===== 字体 ===== */ + --td-font-family: PingFang SC, Microsoft YaHei, Arial Regular; + --td-font-family-medium: PingFang SC, Microsoft YaHei, Arial Medium; + --td-font-size-link-small: 12px; + --td-font-size-link-medium: 14px; + --td-font-size-link-large: 16px; + --td-font-size-mark-small: 12px; + --td-font-size-mark-medium: 14px; + --td-font-size-body-small: 12px; + --td-font-size-body-medium: 14px; + --td-font-size-body-large: 16px; + --td-font-size-title-small: 14px; + --td-font-size-title-medium: 16px; + --td-font-size-title-large: 20px; + --td-font-size-headline-small: 24px; + --td-font-size-headline-medium: 28px; + --td-font-size-headline-large: 36px; + --td-font-size-display-medium: 48px; + --td-font-size-display-large: 64px; + --td-line-height-link-small: 20px; + --td-line-height-link-medium: 22px; + --td-line-height-link-large: 24px; + --td-line-height-mark-small: 20px; + --td-line-height-mark-medium: 22px; + --td-line-height-body-small: 20px; + --td-line-height-body-medium: 22px; + --td-line-height-body-large: 24px; + --td-line-height-title-small: 22px; + --td-line-height-title-medium: 24px; + --td-line-height-title-large: 28px; + --td-line-height-headline-small: 32px; + --td-line-height-headline-medium: 36px; + --td-line-height-headline-large: 44px; + --td-line-height-display-medium: 56px; + --td-line-height-display-large: 72px; + --td-font-link-small: var(--td-font-size-link-small) / var(--td-line-height-link-small) var(--td-font-family); + --td-font-link-medium: var(--td-font-size-link-medium) / var(--td-line-height-link-medium) var(--td-font-family); + --td-font-link-large: var(--td-font-size-link-large) / var(--td-line-height-link-large) var(--td-font-family); + --td-font-mark-small: 600 var(--td-font-size-mark-small) / var(--td-line-height-mark-small) var(--td-font-family); + --td-font-mark-medium: 600 var(--td-font-size-mark-medium) / var(--td-line-height-mark-medium) var(--td-font-family); + --td-font-body-small: var(--td-font-size-body-small) / var(--td-line-height-body-small) var(--td-font-family); + --td-font-body-medium: var(--td-font-size-body-medium) / var(--td-line-height-body-medium) var(--td-font-family); + --td-font-body-large: var(--td-font-size-body-large) / var(--td-line-height-body-large) var(--td-font-family); + --td-font-title-small: 600 var(--td-font-size-title-small) / var(--td-line-height-title-small) var(--td-font-family); + --td-font-title-medium: 600 var(--td-font-size-title-medium) / var(--td-line-height-title-medium) var(--td-font-family); + --td-font-title-large: 600 var(--td-font-size-title-large) / var(--td-line-height-title-large) var(--td-font-family); + --td-font-headline-small: 600 var(--td-font-size-headline-small) / var(--td-line-height-headline-small) var(--td-font-family); + --td-font-headline-medium: 600 var(--td-font-size-headline-medium) / var(--td-line-height-headline-medium) var(--td-font-family); + --td-font-headline-large: 600 var(--td-font-size-headline-large) / var(--td-line-height-headline-large) var(--td-font-family); + --td-font-display-medium: 600 var(--td-font-size-display-medium) / var(--td-line-height-display-medium) var(--td-font-family); + --td-font-display-large: 600 var(--td-font-size-display-large) / var(--td-line-height-display-large) var(--td-font-family); + + /* ===== 尺寸与间距 ===== */ + --td-size-1: 2px; + --td-size-2: 4px; + --td-size-3: 6px; + --td-size-4: 8px; + --td-size-5: 12px; + --td-size-6: 16px; + --td-size-7: 20px; + --td-size-8: 24px; + --td-size-9: 28px; + --td-size-10: 32px; + --td-size-11: 36px; + --td-size-12: 40px; + --td-size-13: 48px; + --td-size-14: 56px; + --td-size-15: 64px; + --td-size-16: 72px; + --td-comp-size-xxxs: var(--td-size-6); + --td-comp-size-xxs: var(--td-size-7); + --td-comp-size-xs: var(--td-size-8); + --td-comp-size-s: var(--td-size-9); + --td-comp-size-m: var(--td-size-10); + --td-comp-size-l: var(--td-size-11); + --td-comp-size-xl: var(--td-size-12); + --td-comp-size-xxl: var(--td-size-13); + --td-comp-size-xxxl: var(--td-size-14); + --td-comp-size-xxxxl: var(--td-size-15); + --td-comp-size-xxxxxl: var(--td-size-16); + --td-pop-padding-s: var(--td-size-2); + --td-pop-padding-m: var(--td-size-3); + --td-pop-padding-l: var(--td-size-4); + --td-pop-padding-xl: var(--td-size-5); + --td-pop-padding-xxl: var(--td-size-6); + --td-comp-paddingLR-xxs: var(--td-size-1); + --td-comp-paddingLR-xs: var(--td-size-2); + --td-comp-paddingLR-s: var(--td-size-4); + --td-comp-paddingLR-m: var(--td-size-5); + --td-comp-paddingLR-l: var(--td-size-6); + --td-comp-paddingLR-xl: var(--td-size-8); + --td-comp-paddingLR-xxl: var(--td-size-10); + --td-comp-paddingTB-xxs: var(--td-size-1); + --td-comp-paddingTB-xs: var(--td-size-2); + --td-comp-paddingTB-s: var(--td-size-4); + --td-comp-paddingTB-m: var(--td-size-5); + --td-comp-paddingTB-l: var(--td-size-6); + --td-comp-paddingTB-xl: var(--td-size-8); + --td-comp-paddingTB-xxl: var(--td-size-10); + --td-comp-margin-xxs: var(--td-size-1); + --td-comp-margin-xs: var(--td-size-2); + --td-comp-margin-s: var(--td-size-4); + --td-comp-margin-m: var(--td-size-5); + --td-comp-margin-l: var(--td-size-6); + --td-comp-margin-xl: var(--td-size-7); + --td-comp-margin-xxl: var(--td-size-8); + --td-comp-margin-xxxl: var(--td-size-10); + --td-comp-margin-xxxxl: var(--td-size-12); + + `; +} + +/** + * TDesign Chat 相关变量(--td-chat-* / --td-chat-md-* / --td-chat-input-*): + * 在 .csk-root 上自包含声明。 + * + * 原因:这些变量在 tdesign-web-components 中定义于 globalCSS 的 constructable + * stylesheet,选择器为 :root。但 adoptedStyleSheets 里的 :root 不匹配 shadow root, + * 导致 shadow DOM 内(t-chat-item / t-chat-md-content)无法解析到这些变量, + * markdown 排版(标题/列表/引用/代码块等)会回退为初始值而"裸奔"。 + * 直接声明在 .csk-root 上即可经 CSS 自定义属性继承穿透 shadow DOM。 + * + * 内容对应 tdesign-web-components v1.2.10 style/index.js 中 css(chat-item + markdown) + * 与 css$1(input)两个 :root 变量块;升级版本时需同步核对。 + */ +function tdChatVars(): string { + return ` + /* ===== chat-input ===== */ + --td-chat-input-attachments-margin: 0 0 var(--td-comp-margin-xs) 0; + --td-chat-input-textarea-max-height: 134px; + --td-chat-input-textarea-scrollbar-width: 4px; + --td-chat-input-textarea-scrollbar-thumb-radius: 2px; + --td-chat-input-padding: var(--td-comp-paddingTB-m) var(--td-comp-paddingLR-m); + --td-chat-input-radius: 16px; + --td-chat-input-shadow: 0px 0px 0px 2px var(--td-brand-color-focus); + --td-chat-input-background: var(--td-bg-color-specialcomponent); + --td-chat-input-hover-border: var(--td-brand-color); + --td-chat-input-actions-margin: 0; + --td-chat-input-actions-gap: var(--td-comp-margin-m); + --td-chat-input-actions-item-wh: 28px; + --td-chat-input-actions-item-radius: 14px; + --td-chat-input-actions-item-font-size: 20px; + --td-chat-input-box-shadow: none; + --td-chat-input-font-size: 16px; + --td-chat-input-content-default-border: var(--td-component-border); + --td-chat-input-textarea-scrollbar-color: var(--td-scrollbar-color); + --td-chat-input-textarea-scrollbar-hover-color: var(--td-scrollbar-hover-color); + --td-chat-input-button-default-bg: var(--td-bg-color-secondarycomponent); + --td-chat-input-button-default-color: var(--td-bg-color-container); + --td-chat-input-actions-item-color: var(--td-text-color-secondary); + --td-chat-input-actions-item-bg: transparent; + --td-chat-input-actions-item-hover-bg: var(--td-bg-color-container-hover); + + /* ===== chat-list / chat-item ===== */ + --td-chat-list-padding-bottom: 0; + --td-chat-list-scroll-button-top: calc(100% - 64px); + --td-chat-list-scroll-button-left: 50%; + --td-chat-list-scroll-button-w: 40px; + --td-chat-list-scroll-button-h: 40px; + --td-chat-list-scroll-button-border: 0.5px solid #dcdcdc; + --td-chat-list-scroll-button-radius: 20px; + --td-chat-list-scroll-button-index: 50; + --td-chat-list-scroll-button-transform: translate(-50%, 0); + --td-chat-item-gap: 38px; + --td-chat-item-header-padding: 0; + --td-chat-item-text-header-padding: 0; + --td-chat-item-header-padding-right: 0; + --td-chat-item-name-padding: 0 var(--td-comp-paddingLR-s) 0 0; + --td-chat-item-name-padding-right: 0 0 0 var(--td-comp-paddingLR-l); + --td-chat-item-name-font: var(--td-font-body-medium); + --td-chat-item-content-gap: var(--td-comp-margin-s); + --td-chat-item-content-max-width: 800px; + --td-chat-item-content-base-margin: 0 0 var(--td-comp-margin-xs) 0; + --td-chat-item-content-base-padding: var(--td-comp-paddingTB-m) var(--td-comp-paddingLR-l); + --td-chat-item-content-base-border-color: transparent; + --td-chat-item-content-padding: var(--td-comp-paddingTB-m) 0; + --td-chat-item-text-error-color: var(--td-error-color); + --td-chat-item-user-text-color: var(--td-text-color-secondary); + --td-chat-item-text-max-width: 100%; + --td-chat-item-text-padding: var(--td-comp-paddingTB-m) var(--td-comp-paddingLR-l); + --td-chat-item-status-margin: var(--td-comp-margin-s) 0 0; + --td-chat-item-text-content-padding: 0; + --td-chat-item-text-detail-padding: 0; + --td-chat-item-text-detail-margin: 0; + --td-chat-item-text-detail-text-margin: 0; + --td-chat-item-text-radius: var(--td-radius-extraLarge); + --td-chat-item-text-border: 1px solid transparent; + --td-chat-item-content-border: 1px solid transparent; + --td-chat-item-content-radius: var(--td-radius-extraLarge); + --td-chat-item-system-margin: 0; + --td-chat-item-system-padding: var(--td-comp-paddingTB-xs) var(--td-comp-paddingLR-s); + --td-chat-item-system-radius: var(--td-radius-medium); + --td-chat-item-system-bg-color: var(--td-bg-color-secondarycontainer); + --td-chat-item-system-color: #afafaf; + --td-chat-item-left-avatar-margin: 0 var(--td-comp-margin-xl) 0 var(--td-comp-margin-xs); + --td-chat-item-right-avatar-margin: 0 var(--td-comp-margin-xs) 0 var(--td-comp-margin-xl); + --td-chat-item-avatar-padding: 6px 0 0 0; + --td-chat-item-avatar-has-header-padding: 30px 0 0 0; + --td-chat-item-image-margin: var(--td-comp-margin-m) 0; + --td-chat-item-think-gap-p: 0 0 var(--td-comp-margin-s) 0; + --td-chat-item-think-padding-tb: var(--td-comp-paddingTB-s); + --td-chat-item-think-padding-lr: var(--td-comp-paddingLR-m); + --td-chat-item-think-border: none; + --td-chat-item-think-radius: var(--td-radius-medium); + --td-chat-item-think-title-gap: var(--td-comp-margin-s); + --td-chat-item-think-status-wh: 20px; + --td-chat-item-think-pending-border-width: 2px; + --td-chat-item-think-header-border: none; + --td-chat-item-think-body-bg: none; + --td-chat-item-think-body-border: none; + --td-chat-item-think-content-margin-top: 2px; + --td-chat-item-think-shadow-top: 0; + --td-chat-item-think-shadow-bottom: 0; + --td-chat-item-think-shadow-margin-top: 0; + --td-chat-item-think-shadow-margin-bottom: 0; + --td-chat-item-think-shadow-top-height: 24px; + --td-chat-item-think-shadow-bottom-height: 24px; + --td-chat-item-think-inner-padding: 0 var(--td-comp-paddingLR-m) var(--td-comp-paddingTB-s) var(--td-comp-paddingLR-m); + --td-chat-item-think-inner-border: solid 2px var(--td-component-stroke); + --td-chat-item-think-inner-border-margin-left: 0px; + --td-chat-item-actions-list-margin: var(--td-comp-margin-xs) 0; + --td-chat-item-text-actions-list-margin: var(--td-comp-margin-xs) 0 0 var(--td-comp-margin-l); + --td-chat-item-actions-list-gap: var(--td-comp-margin-xs); + --td-chat-item-actions-list-padding: var(--td-comp-paddingTB-xs) var(--td-comp-paddingLR-xs); + --td-chat-item-actions-list-radius: var(--td-radius-medium); + --td-chat-item-actions-list-border: 1px solid var(--td-component-border); + --td-chat-item-actions-item-padding: var(--td-comp-paddingTB-xs) var(--td-comp-paddingLR-xs); + --td-chat-item-actions-item-radius: 4px; + --td-chat-item-attachments-margin-top: 12px; + --td-chat-item-image-max-width: 150px; + --td-attachment-image-width: 150px; + --td-chat-item-search-height: 36px; + --td-chat-item-search-padding: var(--td-comp-paddingTB-s) var(--td-comp-paddingLR-s); + --td-chat-item-search-radius: 20px; + --td-chat-item-search-icons-margin-right: 3px; + --td-chat-item-search-icon-width: 16px; + --td-chat-item-search-icon-radius: var(--td-radius-circle); + --td-chat-item-search-icon-margin-left: -3px; + --td-chat-item-search-icon-before-left: -1px; + --td-chat-item-search-icon-before-width: 1px; + --td-chat-item-search-icon-before-height: 100%; + --td-chat-item-search-icon-before-radius: 1px; + --td-chat-item-search-link-color: var(--td-brand-color); + --td-chat-item-search-link-hov-color: var(--td-brand-color-hover); + --td-chat-item-suggestion-gap: var(--td-comp-margin-s); + --td-chat-item-suggestion-height: 38px; + --td-chat-item-suggestion-padding: var(--td-comp-paddingTB-s) var(--td-comp-paddingLR-m); + --td-chat-item-suggestion-radius: var(--td-radius-medium); + --td-chat-item-suggestion-border: solid 1px var(--td-component-border); + --td-chat-item-suggestion-arrow-margin: var(--td-comp-margin-s); + --td-chat-font-size: 16px; + --td-chat-item-system-font-size: 12px; + --td-chat-item-suggestion-font-size: 14px; + --td-chat-item-search-link-line-height: 24px; + --td-chat-list-scroll-button-font-size: 24px; + --td-chat-search-font-size: 12px; + --td-chat-item-content-text-align: left; + --td-chat-item-right-content-text-align: right; + --td-chat-item-text-text-align: left; + --td-chat-list-scroll-button-bg: var(--td-bg-color-container); + --td-chat-list-scroll-button-shadow: 0px 8px 10px -5px rgba(0, 0, 0, 0.08), 0px 16px 24px 2px rgba(0, 0, 0, 0.04), 0px 6px 30px 5px rgba(0, 0, 0, 0.05); + --td-chat-item-error-color: var(--td-error-color); + --td-chat-item-primary-bg: var(--td-bg-color-secondarycontainer); + --td-chat-item-default-bg: var(--td-bg-color-container); + --td-chat-item-outline-border-color: var(--td-component-border); + --td-chat-item-think-bg: var(--td-bg-color-secondarycontainer); + --td-chat-item-think-pending-border-bg-color: #e0f0ff; + --td-chat-item-think-status-pending-color: var(--td-brand-color); + --td-chat-item-think-status-sent-color: var(--td-success-color); + --td-chat-item-think-status-error-color: var(--td-error-color); + --td-chat-item-think-content-color: var(--td-text-color-placeholder); + --td-chat-item-actions-list-bg: var(--td-bg-color-secondarycontainer); + --td-chat-item-actions-item-hover-bg: var(--td-bg-color-component-hover); + --td-chat-item-suggestion-color: var(--td-text-color-primary); + --td-chat-item-suggestion-background: var(--td-bg-color-secondarycontainer); + --td-chat-item-suggestion-arrow-color: var(--td-text-color-placeholder); + + /* ===== chat-md(markdown 排版) ===== */ + --md-heading-color: var(--td-gray-color-7); + --td-chat-md-content-gap-main: 0 0 8px 0; + --td-chat-md-content-gap-t1: 24px 0 20px 0; + --td-chat-md-content-gap-t2: 20px 0 16px 0; + --td-chat-md-content-gap-t3: 16px 0 12px 0; + --td-chat-md-font: var(--td-text-color-primary); + --td-chat-md-padding: var(--td-comp-paddingTB-m) 0; + --td-chat-md-h1-font: 700 22px / 1.6 var(--td-font-family); + --td-chat-md-h2-font: 600 20px / 1.6 var(--td-font-family); + --td-chat-md-h3-font: 600 18px / 1.6 var(--td-font-family); + --td-chat-md-s-color: var(--td-gray-color-7); + --td-chat-md-list-margin: 0 0 8px 2em; + --td-chat-md-list-padding: 0; + --td-chat-md-ul-style: disc; + --td-chat-md-ol-style: decimal; + --td-chat-md-li-margin: 0.5em 0; + --td-chat-md-link-color: var(--td-brand-color-5); + --td-chat-md-link-decoration: none; + --td-chat-md-link-hover-color: var(--td-brand-color-6); + --td-chat-md-code-radius: 8px; + --td-chat-md-code-margin: 0 0 16px 0; + --td-chat-md-code-header-height: 36px; + --td-chat-md-code-header-padding: 0 16px 0 12px; + --td-chat-md-code-header-font: 500 14px / 1.6 var(--td-font-family); + --td-chat-md-code-header-bg: #f3f3f3; + --td-chat-md-code-header-bg-dark: #282c34; + --td-chat-md-code-header-color-dark: #abb2bf; + --td-chat-md-code-header-copy-color: var(--td-gray-color-7); + --td-chat-md-code-header-copy-hover-color: var(--td-gray-color-9); + --td-chat-md-code-body-padding: 10px 16px !important; + --td-chat-md-code-body-bg: #f8f8f8; + --td-chat-md-code-body-overflow: auto; + --td-chat-md-code-body-code-font-size: 14px; + --td-chat-md-katex-block-margin: 1em 0; + --td-chat-md-katex-font: normal 1.21em / 1.2 KaTeX_Main, Times New Roman, serif; + --td-chat-md-blockquote-margin: 16px 0; + --td-chat-md-blockquote-padding: 10px 15px; + --td-chat-md-blockquote-border-left: 4px solid #d9d9d9; + --td-chat-md-blockquote-color: var(--td-gray-color-7); + --td-chat-md-footnote-bg: var(--td-bg-color-page); + --td-chat-md-hr-margin: 16px 0; + --td-chat-md-hr-border: 1px solid rgba(0, 0, 0, 0.08); + --td-chat-md-table-border: 1px solid var(--td-bg-color-secondarycontainer); + --td-chat-md-table-radius: 8px; + --td-chat-md-table-margin: 8px 0; + --td-chat-md-table-font-size: 14px; + --td-chat-md-table-th-padding: 0.66em 1em; + --td-chat-md-table-th-max-width: 448px; + --td-chat-md-table-th-bg: var(--td-bg-color-secondarycontainer); + --td-chat-md-table-th-color: var(--td-text-color-primary); + --td-chat-md-table-th-font: 700 14px / 1.6 var(--td-font-family); + --td-chat-md-table-td-bg-odd: var(--td-bg-color-container); + --td-chat-md-table-td-bg-even: var(--td-bg-color-secondarycontainer); + --td-chat-md-table-td-color: var(--td-text-color-primary); + --td-chat-md-table-td-font: 400 14px / 1.6 var(--td-font-family); + + /* ===== SDK 尺寸覆盖:AI 回复自适应弹窗宽度 + 压缩输入组件 ===== */ + --td-chat-item-content-max-width: 100%; + --td-chat-input-padding: 8px 12px; + --td-chat-input-font-size: 14px; + `; +} + /** 完整 CSS 样式表 */ function getStyles(config: ResolvedConfig): string { return ` /* ChatbotSDK 样式 - csk- 命名空间 */ .csk-root { + ${tdThemeVars(config)} + ${tdChatVars()} ${cssVars(config)} font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif; font-size: 14px; @@ -86,6 +618,112 @@ function getStyles(config: ResolvedConfig): string { } .csk-root *, .csk-root *::before, .csk-root *::after { box-sizing: border-box; } +/* 原生表单控件字体继承(UA 默认不继承 font,宿主无 reset 时用系统字体) */ +.csk-root button, .csk-root input, .csk-root textarea, .csk-root select { font-family: inherit; } +/* launcher/teaser 挂在 body、在 .csk-root 之外,补 box-sizing 防御宿主全局 reset */ +.csk-launcher, .csk-teaser { box-sizing: border-box; } +.csk-launcher *, .csk-teaser * { box-sizing: border-box; } + +/* ===== t-tag(light DOM 组件)样式补注入 ===== + * t-tag 是 tdesign-web-components 的 light DOM 组件(isLightDOM: true),其 .t-tag + * 样式定义在 globalCSS 的 constructable stylesheet 里,但 globalCSS 只对 shadow DOM + * 生效、进不了 light DOM,导致快捷问题/建议标签"裸奔"。此处把 .t-tag 样式补进 light DOM。 + * 来源:tdesign-web-components v1.2.10 的 _chunks/dep-5245073d.js(css_248z)。 */ +.csk-root .t-tag { + font: var(--td-font-body-medium); + color: var(--td-text-color-primary); + box-sizing: border-box; + margin: 0; + padding: 0; + list-style: none; + display: inline-flex; + align-items: center; + flex-direction: row; + padding: 0px var(--td-comp-paddingLR-s); + height: var(--td-comp-size-xs); + font: var(--td-font-body-small); + color: var(--td-text-color-anti); + border-radius: var(--td-radius-default); + border: 1px solid transparent; + white-space: nowrap; +} +.csk-root .t-tag .t-icon { + margin-right: var(--td-comp-margin-xs); + width: calc(var(--td-font-size-body-medium) + 2px); + height: calc(var(--td-font-size-body-medium) + 2px); + flex-shrink: 0; +} +.csk-root .t-tag .t-tag__icon-close { + margin-right: 0; + margin-left: var(--td-comp-margin-s); + font: var(--td-font-body-medium); + cursor: pointer; + transition: color 0.2s cubic-bezier(0.38, 0, 0.24, 1); +} +.csk-root .t-tag--default { color: var(--td-text-color-primary); background-color: var(--td-bg-color-component); } +.csk-root .t-tag--default .t-tag__icon-close { color: var(--td-text-color-placeholder); } +.csk-root .t-tag--default .t-tag__icon-close:hover { color: var(--td-text-color-primary); } +.csk-root .t-tag--default:active { cursor: default; } +.csk-root .t-tag--default.t-tag--disabled { + color: var(--td-text-color-disabled); + background-color: var(--td-bg-color-component-disabled); + border-color: var(--td-component-border); + cursor: not-allowed; +} +.csk-root .t-tag--default.t-tag--disabled:hover { cursor: not-allowed; } +.csk-root .t-tag--default.t-tag--disabled .t-icon:hover { cursor: pointer; } +.csk-root .t-tag--default.t-tag--outline { border-color: var(--td-component-border); } +.csk-root .t-tag--default.t-tag--light { background-color: var(--td-bg-color-secondarycontainer); } +.csk-root .t-tag--default.t-tag--light-outline { border-color: var(--td-component-border); background-color: var(--td-bg-color-secondarycontainer); } +.csk-root .t-tag--primary { background-color: var(--td-brand-color); } +.csk-root .t-tag--primary.t-tag--outline { border-color: var(--td-brand-color); } +.csk-root .t-tag--primary.t-tag--light { background-color: var(--td-brand-color-light); } +.csk-root .t-tag--primary.t-tag--light-outline { border-color: var(--td-brand-color); background-color: var(--td-brand-color-light); } +.csk-root .t-tag--primary.t-tag--light, +.csk-root .t-tag--primary.t-tag--outline, +.csk-root .t-tag--primary.t-tag--light-outline { color: var(--td-brand-color); } +.csk-root .t-tag--success { background-color: var(--td-success-color); } +.csk-root .t-tag--success.t-tag--outline { border-color: var(--td-success-color); } +.csk-root .t-tag--success.t-tag--light { background-color: var(--td-success-color-light); } +.csk-root .t-tag--success.t-tag--light-outline { border-color: var(--td-success-color); background-color: var(--td-success-color-light); } +.csk-root .t-tag--success.t-tag--light, +.csk-root .t-tag--success.t-tag--outline, +.csk-root .t-tag--success.t-tag--light-outline { color: var(--td-success-color); } +.csk-root .t-tag--warning { background-color: var(--td-warning-color); } +.csk-root .t-tag--warning.t-tag--outline { border-color: var(--td-warning-color); } +.csk-root .t-tag--warning.t-tag--light { background-color: var(--td-warning-color-light); } +.csk-root .t-tag--warning.t-tag--light-outline { border-color: var(--td-warning-color); background-color: var(--td-warning-color-light); } +.csk-root .t-tag--warning.t-tag--light, +.csk-root .t-tag--warning.t-tag--outline, +.csk-root .t-tag--warning.t-tag--light-outline { color: var(--td-warning-color); } +.csk-root .t-tag--danger { background-color: var(--td-error-color); } +.csk-root .t-tag--danger.t-tag--outline { border-color: var(--td-error-color); } +.csk-root .t-tag--danger.t-tag--light { background-color: var(--td-error-color-light); } +.csk-root .t-tag--danger.t-tag--light-outline { border-color: var(--td-error-color); background-color: var(--td-error-color-light); } +.csk-root .t-tag--danger .t-tag__icon-close { fill: var(--td-error-color); } +.csk-root .t-tag--danger .t-tag__icon-close:hover { fill: var(--td-error-color-hover); } +.csk-root .t-tag--danger.t-tag--light, +.csk-root .t-tag--danger.t-tag--outline, +.csk-root .t-tag--danger.t-tag--light-outline { color: var(--td-error-color); } +.csk-root .t-tag.t-tag--outline { background: transparent; } +.csk-root .t-tag.t-size-s { padding: 0px var(--td-comp-paddingLR-xs); height: var(--td-comp-size-xxs); font: var(--td-font-body-small); } +.csk-root .t-tag.t-size-s .t-icon { font-size: var(--td-font-body-small); } +.csk-root .t-tag.t-size-l { padding: 0px var(--td-comp-paddingLR-m); height: var(--td-comp-size-m); font: var(--td-font-body-medium); } +.csk-root .t-tag.t-size-l .t-icon { font: var(--td-font-body-medium); } +.csk-root .t-tag.t-tag--round { border-radius: var(--td-radius-extraLarge); } +.csk-root .t-tag.t-tag--mark { border-radius: 0 var(--td-radius-extraLarge) var(--td-radius-extraLarge) 0; } +.csk-root .t-tag.t-tag--ellipsis .t-tag--text { display: inline-block; white-space: nowrap; word-wrap: normal; overflow: hidden; text-overflow: ellipsis; } +.csk-root .t-tag.t-tag--check:not(.t-tag--disabled) { transition: background-color 0.2s cubic-bezier(0.38, 0, 0.24, 1); cursor: pointer; } +.csk-root .t-tag.t-tag--check:not(.t-tag--disabled).t-tag--default.t-tag--dark:not(.t-tag--checked):hover { background-color: var(--td-bg-color-component-hover); } +.csk-root .t-tag.t-tag--check:not(.t-tag--disabled).t-tag--primary.t-tag--dark.t-tag--checked:hover { background-color: var(--td-brand-color-hover); } +.csk-root .t-tag.t-tag--check:not(.t-tag--disabled).t-tag--default.t-tag--outline:not(.t-tag--checked):hover { color: var(--td-brand-color-hover); } +.csk-root .t-tag--check.t-tag--disabled.t-tag--primary.t-tag--dark { background-color: var(--td-brand-color-disabled); cursor: not-allowed; } +.csk-root .t-tag--check.t-tag--disabled.t-tag--primary.t-tag--outline { color: var(--td-text-color-disabled); background-color: var(--td-bg-color-component-disabled); border-color: var(--td-text-color-disabled); cursor: not-allowed; } +.csk-root .t-check-tag-group .t-tag:not(:last-child) { margin-right: var(--td-comp-margin-s); } +.csk-root a .t-tag { cursor: pointer; transition: all 0.2s linear; } +.csk-root a .t-tag:hover { color: var(--td-brand-color); background-color: var(--td-bg-color-component-hover); } +.csk-root a .t-tag:active { color: var(--td-brand-color-active); background-color: var(--td-bg-color-component-active); } + /* ========== 悬浮按钮 ========== */ .csk-launcher { position: fixed;