/** * 样式注入模块 - CSS 自动注入,csk- 命名空间隔离 * * 设计语言:现代克制、柔和阴影、圆角气泡、主色渐变、入场动效。 * 主题色由配置驱动,通过 CSS 变量透传,可在运行时热替换。 */ import { ResolvedConfig } from './types'; let styleElement: HTMLStyleElement | null = null; /** 将 hex 颜色解析为 "r, g, b" 字符串,用于 rgba() 拼接 */ function hexToRgb(hex: string): string { const match = hex.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/); if (!match) return '79, 70, 229'; 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); const lighter = adjustColor(config.primaryColor, 18); const rgb = hexToRgb(config.primaryColor); return ` --csk-primary: ${config.primaryColor}; --csk-primary-hover: ${darker}; --csk-primary-light: ${lighter}; --csk-primary-rgb: ${rgb}; --csk-bg-user: linear-gradient(135deg, ${config.primaryColor}, ${darker}); --csk-bg-ai: #F3F3F3; --csk-text-user: #ffffff; --csk-text-ai: rgba(0, 0, 0, 0.9); --csk-window-width: ${config.width}px; --csk-window-height: ${config.height}px; --csk-radius: 16px; --csk-shadow-window: 0 12px 48px rgba(15, 23, 42, 0.18), 0 2px 8px rgba(15, 23, 42, 0.06); --csk-shadow-bubble: 0 1px 10px rgba(0, 0, 0, 0.05), 0 4px 5px rgba(0, 0, 0, 0.08); --csk-border: #E8E8E8; --csk-border-strong: #DDDDDD; --csk-text-secondary: rgba(0, 0, 0, 0.6); --csk-text-muted: rgba(0, 0, 0, 0.4); --csk-bg-app: #FFFFFF; `; } /** 简单的颜色加深(按通道加减) */ function adjustColor(hex: string, amount: number): string { const match = hex.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/); if (!match) { return hex; } const clamp = (v: number) => Math.max(0, Math.min(255, v)); const r = clamp(parseInt(match[1], 16) + amount); const g = clamp(parseInt(match[2], 16) + amount); const b = clamp(parseInt(match[3], 16) + amount); 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; line-height: 1.55; color: #1F2937; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; box-sizing: border-box; } .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; bottom: 24px; z-index: 9998; width: 60px; height: 60px; border-radius: 50%; /* 白色背景,简洁克制 */ background: #ffffff; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #fff; user-select: none; overflow: hidden; /* 浅灰边框 */ border: 2px solid #E5E7EB; /* 简洁柔和阴影 */ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 4px 16px rgba(0, 0, 0, 0.06); transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.25s ease, border-color 0.25s ease; animation: csk-launcher-in 0.55s cubic-bezier(0.22, 1, 0.36, 1) both; } .csk-launcher--right { right: 24px; } .csk-launcher--left { left: 24px; } .csk-launcher:hover { transform: translateY(-3px) scale(1.08); border-color: #D1D5DB; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.10), 0 8px 24px rgba(0, 0, 0, 0.08); } .csk-launcher:active { transform: scale(0.93); } .csk-launcher svg { width: 30px; height: 30px; position: relative; z-index: 1; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.15)); } .csk-launcher img { width: 100%; height: 100%; object-fit: cover; position: relative; z-index: 1; -webkit-user-drag: none; user-select: none; } @keyframes csk-launcher-in { 0% { opacity: 0; transform: scale(0.5) translateY(16px); } 60% { transform: scale(1.06) translateY(-2px); } 100% { opacity: 1; transform: scale(1) translateY(0); } } /* ========== 聊天弹窗 ========== */ .csk-window { position: fixed; bottom: 24px; z-index: 9999; width: var(--csk-window-width); height: var(--csk-window-height); max-height: calc(100vh - 48px); background: #fff; border-radius: var(--csk-radius); box-shadow: var(--csk-shadow-window); display: flex; flex-direction: column; overflow: hidden; transform-origin: bottom right; transition: opacity 0.24s ease, transform 0.24s cubic-bezier(0.34, 1.2, 0.64, 1), visibility 0.24s; opacity: 1; transform: translateY(0) scale(1); visibility: visible; pointer-events: auto; } .csk-window--right { right: 24px; transform-origin: bottom right; } .csk-window--left { left: 24px; transform-origin: bottom left; } .csk-window--hidden { opacity: 0; transform: translateY(12px) scale(0.96); visibility: hidden; pointer-events: none; } /* ========== 水印层 ========== */ .csk-watermark { position: absolute; top: 0; left: 0; right: 0; bottom: 0; pointer-events: none; user-select: none; z-index: 0; background-repeat: repeat; opacity: 0.8; } /* ========== 头部 ========== */ .csk-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; min-height: 60px; background: var(--csk-bg-user); color: #fff; cursor: move; user-select: none; position: relative; flex-shrink: 0; } .csk-header::after { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 24px; background: linear-gradient(to bottom, rgba(0,0,0,0.04), transparent); pointer-events: none; } .csk-header__left { display: flex; align-items: center; gap: 12px; min-width: 0; } .csk-header__avatar { width: 38px; height: 38px; border-radius: 50%; background: rgba(255, 255, 255, 0.22); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; flex-shrink: 0; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25); } .csk-header__avatar svg { width: 22px; height: 22px; } .csk-header__info { display: flex; flex-direction: column; min-width: 0; } .csk-header__title { font-size: 15px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; letter-spacing: 0.2px; } .csk-header__status { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; opacity: 0.9; margin-top: 2px; } .csk-status-dot { width: 7px; height: 7px; border-radius: 50%; background: #6EE7B7; box-shadow: 0 0 0 0 rgba(110, 231, 183, 0.7); animation: csk-status-pulse 2s infinite; } @keyframes csk-status-pulse { 0% { box-shadow: 0 0 0 0 rgba(110, 231, 183, 0.7); } 70% { box-shadow: 0 0 0 6px rgba(110, 231, 183, 0); } 100% { box-shadow: 0 0 0 0 rgba(110, 231, 183, 0); } } .csk-header__actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; } .csk-header__btn, .csk-history-btn { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; background: transparent; color: #fff; cursor: pointer; border-radius: 8px; transition: background 0.15s, transform 0.15s; } .csk-header__btn:hover, .csk-history-btn:hover { background: rgba(255, 255, 255, 0.2); } .csk-header__btn:active, .csk-history-btn:active { transform: scale(0.92); } /* ========== 角色选择器 ========== */ .csk-role-select-wrap { display: flex; align-items: center; flex-shrink: 0; margin: 0 4px; } /* ========== 消息区 ========== */ .csk-messages { flex: 1; overflow-y: auto; padding: 16px; background: var(--csk-bg-app); scroll-behavior: smooth; } .csk-messages::-webkit-scrollbar { width: 6px; } .csk-messages::-webkit-scrollbar-track { background: transparent; } .csk-messages::-webkit-scrollbar-thumb { background: #D8DCE3; border-radius: 3px; } .csk-messages::-webkit-scrollbar-thumb:hover { background: #C2C7D0; } /* 欢迎空状态 */ .csk-welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 48px 24px 32px; color: #6B7280; animation: csk-fade-in 0.3s ease; } .csk-welcome__avatar { width: 56px; height: 56px; border-radius: 50%; background: var(--csk-bg-user); color: #fff; display: flex; align-items: center; justify-content: center; margin-bottom: 14px; box-shadow: 0 6px 18px rgba(var(--csk-primary-rgb), 0.3); } .csk-welcome__avatar svg { width: 30px; height: 30px; } .csk-welcome__title { font-size: 15px; font-weight: 600; color: #1F2937; margin-bottom: 4px; } .csk-welcome__desc { font-size: 13px; color: #9CA3AF; line-height: 1.6; } /* 消息气泡 */ /* 注意:.csk-msg 为块级纵向容器。头像 + 气泡的横向对齐由 t-chat-item 内部(placement 属性)完成, sources / suggestions 直接 append 到 wrapper 末尾,须纵向堆叠在气泡下方,不能用 flex row 排布 */ .csk-msg { margin-bottom: 16px; max-width: 100%; word-break: break-word; animation: csk-msg-in 0.28s cubic-bezier(0.22, 1, 0.36, 1); } @keyframes csk-msg-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } @keyframes csk-fade-in { from { opacity: 0; } to { opacity: 1; } } /* ========== 输入区 ========== */ .csk-input-area { padding: 8px 12px 10px; background: #fff; border-top: 1px solid var(--csk-border); flex-shrink: 0; } /* ========== 保密声明脚注 ========== */ .csk-disclaimer { margin-top: 4px; padding: 4px 8px 2px; font-size: 10px; color: #9CA3AF; line-height: 1.4; user-select: none; cursor: pointer; } .csk-disclaimer__title { font-weight: 600; margin-bottom: 2px; color: #9CA3AF; } /* 折叠态:默认只显示首行(标题),其余条文隐藏;标题末尾加 ▾ 提示可展开 */ .csk-disclaimer > :not(:first-child) { display: none; } .csk-disclaimer > :first-child::after { content: ' ▾'; } .csk-disclaimer--expanded > :not(:first-child) { display: block; } .csk-disclaimer--expanded > :first-child::after { content: ' ▴'; } .csk-dark .csk-disclaimer { color: #6B7280; } .csk-dark .csk-disclaimer__title { color: #6B7280; } /* ========== 新对话按钮 ========== */ .csk-clear-btn { display: inline-flex; align-items: center; gap: 5px; padding: 5px 12px; border: 1px solid var(--csk-border); border-radius: 999px; background: #fff; color: #6B7280; font-size: 12px; cursor: pointer; margin: 0 auto 8px; transition: all 0.15s; font-family: inherit; } .csk-clear-btn:hover { background: #FEF2F2; border-color: #FCA5A5; color: #DC2626; } /* ========== P1: 知识库分类下拉 ========== */ .csk-category-bar { display: flex; align-items: center; padding: 8px 12px; border-top: 1px solid var(--csk-border); background: #FBFBFC; gap: 8px; flex-shrink: 0; } .csk-category-bar__label { font-size: 13px; white-space: nowrap; } /* ========== P1: RAG 引用来源卡片 ========== */ .csk-sources { margin-top: 8px; max-width: 100%; font-size: 12px; } .csk-sources__body { margin-top: 6px; border: 1px solid var(--csk-border); border-radius: 8px; overflow: hidden; background: #fff; } .csk-sources--collapsed .csk-sources__body { display: none; } .csk-source-item { padding: 6px 10px; border-bottom: 1px solid #F0F1F4; transition: background 0.15s; } .csk-source-item:last-child { border-bottom: none; } .csk-source-item:hover { background: var(--csk-bg-ai); } .csk-source-item__name { font-weight: 500; color: #1F2937; margin-bottom: 2px; } .csk-source-item__snippet { color: #6B7280; line-height: 1.5; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .csk-source-item__meta { font-size: 11px; color: #9CA3AF; margin-top: 2px; } /* ========== P2: 会话管理面板 ========== */ .csk-history-panel { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: #fff; z-index: 10; display: flex; flex-direction: column; overflow: hidden; animation: csk-slide-in 0.24s ease; } .csk-history-panel--hidden { display: none; } @keyframes csk-slide-in { from { transform: translateX(100%); opacity: 0.4; } to { transform: translateX(0); opacity: 1; } } .csk-history-panel__header { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid var(--csk-border); background: var(--csk-bg-app); flex-shrink: 0; } .csk-history-panel__title { font-size: 14px; font-weight: 600; color: #1F2937; } .csk-history-panel__back { display: flex; align-items: center; gap: 4px; padding: 5px 12px; border: 1px solid var(--csk-border); border-radius: 8px; background: #fff; color: #374151; font-size: 12px; cursor: pointer; transition: all 0.15s; font-family: inherit; } .csk-history-panel__back:hover { background: #F3F4F6; } .csk-history-panel__list { flex: 1; overflow-y: auto; padding: 10px; } .csk-history-panel__list::-webkit-scrollbar { width: 5px; } .csk-history-panel__list::-webkit-scrollbar-thumb { background: #E5E7EB; border-radius: 2px; } .csk-history-item { display: flex; align-items: center; justify-content: space-between; padding: 11px 12px; border-radius: 10px; cursor: pointer; transition: background 0.15s; margin-bottom: 5px; border: 1px solid transparent; } .csk-history-item:hover { background: #F3F4F6; } .csk-history-item--active { background: rgba(var(--csk-primary-rgb), 0.08); border-color: rgba(var(--csk-primary-rgb), 0.2); } .csk-history-item--active:hover { background: rgba(var(--csk-primary-rgb), 0.12); } .csk-history-item__info { flex: 1; min-width: 0; } .csk-history-item__id { font-size: 13px; font-weight: 500; color: #1F2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .csk-history-item__meta { font-size: 11px; color: #9CA3AF; margin-top: 3px; } .csk-history-item__actions { display: flex; gap: 4px; margin-left: 8px; opacity: 0; transition: opacity 0.15s; } .csk-history-item:hover .csk-history-item__actions { opacity: 1; } .csk-history-action { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: none; border-radius: 7px; cursor: pointer; font-size: 12px; transition: all 0.15s; } .csk-history-action--export { background: #EFF6FF; color: #2563EB; } .csk-history-action--export:hover { background: #DBEAFE; } .csk-history-action--delete { background: #FEF2F2; color: #DC2626; } .csk-history-action--delete:hover { background: #FEE2E2; } .csk-history-panel__empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 48px 20px; color: #9CA3AF; font-size: 13px; text-align: center; } .csk-history-panel__empty-icon { font-size: 34px; margin-bottom: 10px; opacity: 0.5; } .csk-history-panel__loading { display: flex; align-items: center; justify-content: center; padding: 24px; color: #9CA3AF; font-size: 13px; } /* ========== 快捷问题芯片 ========== */ .csk-quick-replies { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 18px; max-width: 320px; } /* ========== 推荐问题(suggest-message-list) ========== */ .csk-suggestions { margin-top: 6px; padding: 4px 0 0 0; } .csk-suggestions__list { display: flex; flex-wrap: wrap; gap: 8px; } /* ========== 新消息提示按钮 ========== */ .csk-newmsg { position: absolute; bottom: 82px; left: 50%; transform: translateX(-50%); z-index: 5; display: inline-flex; align-items: center; gap: 5px; padding: 7px 14px; border: none; background: var(--csk-primary); color: #fff; font-size: 12px; font-family: inherit; border-radius: 999px; cursor: pointer; box-shadow: 0 4px 14px rgba(var(--csk-primary-rgb), 0.38); transition: transform 0.2s ease, box-shadow 0.2s ease; animation: csk-msg-in 0.25s ease; white-space: nowrap; } .csk-newmsg:hover { transform: translateX(-50%) translateY(-1px); box-shadow: 0 6px 18px rgba(var(--csk-primary-rgb), 0.48); } .csk-newmsg:active { transform: translateX(-50%) scale(0.95); } .csk-newmsg--hidden { display: none; } /* ========== 窗口缩放拖拽手柄 ========== */ .csk-resize-handle { position: absolute; right: 4px; bottom: 4px; width: 20px; height: 20px; display: flex; align-items: flex-end; justify-content: flex-end; cursor: nwse-resize; z-index: 5; user-select: none; -webkit-user-select: none; /* 圆形半透明背景,与整体圆润风格一致 */ border-radius: 50%; background: transparent; transition: background 0.2s ease; } .csk-resize-handle:hover { background: rgba(var(--csk-primary-rgb), 0.08); } /* 三斜线抓手 — 纯 CSS 绘制,用两条间距微调的线,视觉上是三条 */ .csk-resize-grip { display: block; width: 10px; height: 10px; background: linear-gradient(135deg, transparent 0%, transparent 38%, #C8CCD2 38%, #C8CCD2 42%, transparent 42%, transparent 54%, #C8CCD2 54%, #C8CCD2 58%, transparent 58%, transparent 70%, #C8CCD2 70%, #C8CCD2 74%, transparent 74% ); transition: background 0.2s ease; border-radius: 2px; } /* hover 时线条变为主色 */ .csk-resize-handle:hover .csk-resize-grip { background: linear-gradient(135deg, transparent 0%, transparent 38%, var(--csk-primary) 38%, var(--csk-primary) 42%, transparent 42%, transparent 54%, var(--csk-primary) 54%, var(--csk-primary) 58%, transparent 58%, transparent 70%, var(--csk-primary) 70%, var(--csk-primary) 74%, transparent 74% ); } /* 暗色模式 */ .csk-dark .csk-resize-grip { background: linear-gradient(135deg, transparent 0%, transparent 38%, #5B6070 38%, #5B6070 42%, transparent 42%, transparent 54%, #5B6070 54%, #5B6070 58%, transparent 58%, transparent 70%, #5B6070 70%, #5B6070 74%, transparent 74% ); } .csk-dark .csk-resize-handle:hover .csk-resize-grip { background: linear-gradient(135deg, transparent 0%, transparent 38%, var(--csk-primary-light) 38%, var(--csk-primary-light) 42%, transparent 42%, transparent 54%, var(--csk-primary-light) 54%, var(--csk-primary-light) 58%, transparent 58%, transparent 70%, var(--csk-primary-light) 70%, var(--csk-primary-light) 74%, transparent 74% ); } /* 缩放拖拽中:禁用过渡动画 + 全局光标 */ .csk-window--resizing { transition: none !important; user-select: none; } .csk-window--resizing * { cursor: nwse-resize !important; } /* ========== 暗色模式 ========== */ .csk-root.csk-dark { --csk-bg-ai: #1E1E2E; --csk-text-ai: #E2E8F0; --csk-border: #3B3B52; --csk-border-strong: #454560; --csk-text-secondary: rgba(255, 255, 255, 0.6); --csk-text-muted: rgba(255, 255, 255, 0.4); --csk-bg-app: #141421; /* TDesign 底层语义变量暗色覆盖:让 t-chat-item/t-chat-sender/t-tag 等适配暗色 语义变量(--td-bg-color-container / --td-text-color-primary 等)都引用这几个底层变量 */ --td-gray-color-1: #1E1E2E; --td-gray-color-2: #141421; --td-gray-color-3: #3B3B52; --td-gray-color-4: #454560; --td-font-gray-1: rgba(255, 255, 255, 0.9); --td-font-gray-2: rgba(255, 255, 255, 0.6); --td-font-gray-3: rgba(255, 255, 255, 0.4); color: #E2E8F0; } .csk-dark .csk-window { background: #1A1A2E; box-shadow: 0 12px 48px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.2); } .csk-dark .csk-watermark { opacity: 1; } .csk-dark .csk-header::after { background: linear-gradient(to bottom, rgba(0,0,0,0.12), transparent); } .csk-dark .csk-input-area { background: #1A1A2E; border-top-color: var(--csk-border); } .csk-dark .csk-category-bar { background: #1A1A2E; border-top-color: var(--csk-border); } .csk-dark .csk-sources__body { background: #1E1E2E; border-color: var(--csk-border); } .csk-dark .csk-source-item:hover { background: var(--csk-bg-ai); } .csk-dark .csk-source-item__name { color: #E2E8F0; } .csk-dark .csk-source-item__meta { color: #6B7280; } .csk-dark .csk-history-panel { background: #1A1A2E; } .csk-dark .csk-history-panel__header { background: var(--csk-bg-app); border-bottom-color: var(--csk-border); } .csk-dark .csk-history-panel__back { background: var(--csk-bg-app); border-color: var(--csk-border); color: #E2E8F0; } .csk-dark .csk-history-panel__back:hover { background: #2D2D48; } .csk-dark .csk-history-item:hover { background: #25253A; } .csk-dark .csk-history-item__id { color: #E2E8F0; } .csk-dark .csk-history-item__meta { color: #6B7280; } .csk-dark .csk-history-item--active { background: rgba(var(--csk-primary-rgb), 0.15); } .csk-dark .csk-history-item--active:hover { background: rgba(var(--csk-primary-rgb), 0.22); } .csk-dark .csk-clear-btn { background: var(--csk-bg-app); border-color: var(--csk-border); color: #9CA3AF; } .csk-dark .csk-clear-btn:hover { background: #3B1A1A; border-color: #7F1D1D; color: #FCA5A5; } .csk-dark .csk-welcome__title { color: #E2E8F0; } .csk-dark .csk-welcome__desc { color: #6B7280; } .csk-dark .csk-teaser { background: #2D2D48; color: #E2E8F0; box-shadow: 0 4px 20px rgba(0,0,0,0.35); } .csk-dark .csk-teaser::after { border-top-color: #2D2D48; } .csk-dark .csk-teaser__close { color: #6B7280; } .csk-dark .csk-teaser__close:hover { color: #9CA3AF; } .csk-dark .csk-history-panel__search-wrap { background: var(--csk-bg-app); border-color: var(--csk-border); } .csk-dark .csk-history-panel__search-wrap:focus-within { border-color: var(--csk-primary); } .csk-dark .csk-history-panel__search { color: #E2E8F0; } .csk-dark .csk-launcher__badge { box-shadow: 0 0 0 2px #1A1A2E; } .csk-dark .csk-welcome__avatar { box-shadow: 0 6px 18px rgba(var(--csk-primary-rgb), 0.25); } .csk-dark .csk-header__avatar { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.15); background: rgba(255, 255, 255, 0.1); } /* Launcher 拖拽态:微放大 + 阴影加深 */ .csk-launcher--dragging { cursor: grabbing !important; transform: scale(1.1) !important; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.14), 0 12px 32px rgba(0, 0, 0, 0.18), 0 14px 40px rgba(236, 72, 153, 0.36), 0 0 56px rgba(236, 72, 153, 0.18) !important; } /* 拖拽中隐藏未读徽章 */ .csk-launcher--dragging .csk-launcher__badge { display: none !important; } /* 吸附过渡动画(释放时的缓动) */ .csk-launcher--snap { transition: left 0.3s ease-out, right 0.3s ease-out, bottom 0.3s ease-out !important; } /* ========== Launcher 未读徽章 ========== */ .csk-launcher__badge { position: absolute; top: -2px; right: -2px; width: 16px; height: 16px; border-radius: 50%; background: #EF4444; border: 2px solid #fff; box-shadow: 0 0 0 2px #fff; animation: csk-badge-in 0.28s cubic-bezier(0.34, 1.56, 0.64, 1); pointer-events: none; } .csk-launcher__badge--hidden { display: none; } @keyframes csk-badge-in { from { transform: scale(0); } to { transform: scale(1); } } /* ========== Launcher 提示气泡 ========== */ .csk-teaser { position: fixed; bottom: 90px; z-index: 9997; max-width: 240px; padding: 10px 14px; background: #fff; color: #1F2937; font-size: 13px; line-height: 1.5; border-radius: 12px; box-shadow: 0 4px 20px rgba(15, 23, 42, 0.15); animation: csk-teaser-in 0.35s cubic-bezier(0.22, 1, 0.36, 1); cursor: pointer; user-select: none; } .csk-teaser--right { right: 24px; } .csk-teaser--left { left: 24px; } .csk-teaser::after { content: ''; position: absolute; bottom: -6px; right: 28px; width: 12px; height: 12px; background: inherit; border-radius: 2px; transform: rotate(45deg); box-shadow: 2px 2px 4px rgba(15, 23, 42, 0.08); } .csk-teaser--left::after { right: auto; left: 28px; } .csk-teaser__close { position: absolute; top: 4px; right: 6px; background: none; border: none; font-size: 16px; color: #9CA3AF; cursor: pointer; padding: 2px 4px; line-height: 1; border-radius: 4px; } .csk-teaser__close:hover { color: #374151; } .csk-teaser--hidden { display: none; } @keyframes csk-teaser-in { from { opacity: 0; transform: translateY(8px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } } /* ========== 点踩原因选择面板 ========== */ .csk-feedback-reason { position: relative; margin-top: 6px; padding: 10px 12px; background: #fff; border: 1px solid var(--csk-border); border-radius: 8px; animation: csk-fade-in 0.15s ease; } .csk-feedback-reason__title { font-size: 12px; color: var(--csk-text-muted); margin-bottom: 8px; } .csk-feedback-reason__options { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; } .csk-feedback-reason__btn { padding: 4px 10px; font-size: 12px; border: 1px solid var(--csk-border); border-radius: 14px; background: #fff; color: #1F2937; cursor: pointer; transition: all 0.15s ease; } .csk-feedback-reason__btn:hover { border-color: var(--csk-primary); color: var(--csk-primary); background: rgba(var(--csk-primary-rgb), 0.08); } .csk-feedback-reason__comment-row { display: flex; gap: 6px; } .csk-feedback-reason__comment { flex: 1; padding: 4px 8px; font-size: 12px; border: 1px solid var(--csk-border); border-radius: 6px; background: #fff; color: #1F2937; outline: none; } .csk-feedback-reason__comment:focus { border-color: var(--csk-primary); } .csk-feedback-reason__submit { padding: 4px 10px; font-size: 12px; border: none; border-radius: 6px; background: var(--csk-primary); color: #fff; cursor: pointer; white-space: nowrap; } .csk-feedback-reason__close { position: absolute; top: 8px; right: 8px; width: 18px; height: 18px; border: none; background: none; color: var(--csk-text-muted); cursor: pointer; padding: 0; display: flex; align-items: center; justify-content: center; border-radius: 4px; } .csk-feedback-reason__close:hover { color: #1F2937; background: #F3F4F6; } .csk-dark .csk-feedback-reason { background: #1A1A2E; border-color: var(--csk-border); } .csk-dark .csk-feedback-reason__btn { background: rgba(255,255,255,0.06); border-color: var(--csk-border); color: #E2E8F0; } .csk-dark .csk-feedback-reason__btn:hover { border-color: var(--csk-primary-light); color: var(--csk-primary-light); background: rgba(var(--csk-primary-rgb), 0.15); } .csk-dark .csk-feedback-reason__comment { background: rgba(255,255,255,0.06); border-color: var(--csk-border); color: #E2E8F0; } /* ========== 历史会话搜索框 ========== */ .csk-history-panel__search-wrap { padding: 8px 10px; border-bottom: 1px solid var(--csk-border); flex-shrink: 0; } .csk-history-panel__search-wrap:focus-within { border-bottom-color: var(--csk-primary); } .csk-history-panel__search { width: 100%; padding: 7px 10px; border: 1px solid var(--csk-border); border-radius: 8px; font-size: 12.5px; font-family: inherit; color: #1F2937; background: var(--csk-bg-app); outline: none; transition: border-color 0.2s; } .csk-history-panel__search:focus { border-color: var(--csk-primary); } .csk-history-panel__search::placeholder { color: #9CA3AF; } /* ========== a11y 焦点指示 ========== */ .csk-root :focus-visible { outline: 2px solid var(--csk-primary); outline-offset: 2px; } .csk-root button:focus-visible { outline: 2px solid var(--csk-primary); outline-offset: 2px; border-radius: inherit; } /* 弹窗打开时隐藏 launcher 上的焦点环 */ .csk-launcher:focus-visible { outline: none; border-color: rgba(255, 255, 255, 0.75); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 4px 16px rgba(0, 0, 0, 0.10), 0 6px 24px rgba(var(--csk-primary-rgb), 0.28), 0 0 48px rgba(var(--csk-primary-rgb), 0.12), 0 0 0 4px rgba(var(--csk-primary-rgb), 0.3); } /* ========== a11y aria-live 播报区(视觉隐藏) ========== */ .csk-sr-only { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0,0,0,0) !important; white-space: nowrap !important; border: 0 !important; } /* ========== 会话分组标题 ========== */ .csk-history-group { margin-bottom: 4px; } .csk-history-group__label { font-size: 11px; font-weight: 600; color: #9CA3AF; text-transform: uppercase; letter-spacing: 0.5px; padding: 8px 12px 4px; user-select: none; } .csk-dark .csk-history-group__label { color: #6B7280; } /* ========== 移动端适配 ========== */ @media (max-width: 480px) { .csk-window { width: 100vw !important; height: 100vh !important; max-height: 100vh; bottom: 0 !important; right: 0 !important; left: 0 !important; border-radius: 0; } .csk-resize-handle { display: none !important; } .csk-window--hidden { transform: translateY(100%); } /* 移动端默认位置,仅在未被 JS 拖拽覆盖时生效 */ .csk-launcher:not([style*="bottom"]) { bottom: 20px; } .csk-launcher--right:not([style*="bottom"]) { right: 20px; } .csk-launcher--left:not([style*="bottom"]) { left: 20px; } } `; } /** * 注入样式到 document.head */ export function injectStyles(config: ResolvedConfig): void { // 避免重复注入 if (document.querySelector('style[data-csk-sdk]')) { return; } styleElement = document.createElement('style'); styleElement.setAttribute('data-csk-sdk', ''); styleElement.textContent = getStyles(config); document.head.appendChild(styleElement); } /** * 移除注入的样式 */ export function removeStyles(): void { if (styleElement && styleElement.parentNode) { styleElement.parentNode.removeChild(styleElement); styleElement = null; } document.querySelectorAll('style[data-csk-sdk]').forEach((el) => el.remove()); }