/** * 样式注入模块 - 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)}`; } /** 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: #ffffff; --csk-text-user: #ffffff; --csk-text-ai: #1F2937; --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 2px rgba(15, 23, 42, 0.06); --csk-border: #ECEEF2; --csk-bg-app: #F6F7F9; `; } /** 简单的颜色加深(按通道加减) */ 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')}`; } /** 完整 CSS 样式表 */ function getStyles(config: ResolvedConfig): string { return ` /* ChatbotSDK 样式 - csk- 命名空间 */ .csk-root { ${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; } /* ========== 悬浮按钮 ========== */ .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: 36px; height: 36px; object-fit: contain; 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-role-select { appearance: none; -webkit-appearance: none; padding: 4px 24px 4px 10px; border: 1px solid rgba(255, 255, 255, 0.35); border-radius: 8px; background: rgba(255, 255, 255, 0.15) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='white' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat right 6px center; color: #fff; font-size: 12px; font-family: inherit; cursor: pointer; outline: none; transition: background 0.15s, border-color 0.15s; max-width: 120px; text-overflow: ellipsis; white-space: nowrap; } .csk-role-select:hover { background-color: rgba(255, 255, 255, 0.25); border-color: rgba(255, 255, 255, 0.55); } .csk-role-select:focus { border-color: rgba(255, 255, 255, 0.7); box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.2); } .csk-role-select option { color: #1F2937; background: #fff; } /* ========== 消息区 ========== */ .csk-messages { flex: 1; overflow-y: auto; padding: 20px 16px 12px; 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-offline-banner { display: flex; align-items: center; justify-content: center; gap: 6px; padding: 8px 12px; margin: 0 0 8px 0; border-radius: 8px; font-size: 12px; line-height: 1.5; background: #FEF2F2; color: #991B1B; border: 1px solid #FECACA; } .csk-dark .csk-offline-banner { background: #450A0A; color: #FCA5A5; border-color: #7F1D1D; } /* 欢迎空状态 */ .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 { display: flex; margin-bottom: 18px; max-width: 100%; word-break: break-word; animation: csk-msg-in 0.28s cubic-bezier(0.22, 1, 0.36, 1); } .csk-msg--user { flex-direction: row-reverse; align-items: flex-end; } .csk-msg--ai { flex-direction: row; align-items: flex-start; } .csk-msg__avatar { width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin: 0 8px; box-shadow: var(--csk-shadow-bubble); } .csk-msg__avatar svg { width: 18px; height: 18px; } .csk-msg__avatar--ai { background: #fff; color: var(--csk-primary); border: 1px solid var(--csk-border); } .csk-msg__avatar--user { background: var(--csk-bg-user); color: #fff; } .csk-msg__content { display: flex; flex-direction: column; min-width: 0; max-width: calc(100% - 46px); } .csk-msg--user .csk-msg__content { align-items: flex-end; } .csk-msg--ai .csk-msg__content { align-items: flex-start; } .csk-msg__bubble { padding: 10px 14px; border-radius: 16px; font-size: 14px; line-height: 1.6; box-shadow: var(--csk-shadow-bubble); } .csk-msg--user .csk-msg__bubble { background: var(--csk-bg-user); color: var(--csk-text-user); border-radius: 16px 16px 4px 16px; } .csk-msg--ai .csk-msg__bubble { background: var(--csk-bg-ai); color: var(--csk-text-ai); border-radius: 16px 16px 16px 4px; border: 1px solid var(--csk-border); } .csk-msg__time { font-size: 11px; color: #9CA3AF; margin-top: 5px; padding: 0 4px; } @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; } } /* ========== Loading 动画 ========== */ .csk-loading { display: flex; align-items: center; margin-bottom: 18px; animation: csk-msg-in 0.28s ease; } .csk-loading__avatar { width: 30px; height: 30px; border-radius: 50%; background: #fff; color: var(--csk-primary); border: 1px solid var(--csk-border); display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin: 0 8px; box-shadow: var(--csk-shadow-bubble); } .csk-loading__avatar svg { width: 18px; height: 18px; } .csk-loading__bubble { background: #fff; border: 1px solid var(--csk-border); border-radius: 16px 16px 16px 4px; padding: 12px 16px; display: flex; align-items: center; gap: 5px; } .csk-loading__dot { width: 7px; height: 7px; border-radius: 50%; background: #B4B9C2; animation: csk-bounce 1.4s ease-in-out infinite both; } .csk-loading__dot:nth-child(1) { animation-delay: 0s; } .csk-loading__dot:nth-child(2) { animation-delay: 0.16s; } .csk-loading__dot:nth-child(3) { animation-delay: 0.32s; } @keyframes csk-bounce { 0%, 80%, 100% { transform: scale(0.6); opacity: 0.6; } 40% { transform: scale(1); opacity: 1; } } /* ========== 输入区 ========== */ .csk-input-area { padding: 10px 12px 14px; background: #fff; border-top: 1px solid var(--csk-border); flex-shrink: 0; } .csk-input-wrap { display: flex; align-items: flex-end; gap: 8px; background: var(--csk-bg-app); border: 1px solid var(--csk-border); border-radius: 14px; padding: 6px 6px 6px 14px; transition: border-color 0.2s, box-shadow 0.2s, background 0.2s; } .csk-input-wrap--focus { border-color: var(--csk-primary); background: #fff; box-shadow: 0 0 0 3px rgba(var(--csk-primary-rgb), 0.12); } .csk-input { flex: 1; border: none; background: transparent; padding: 8px 0; font-size: 14px; outline: none; font-family: inherit; resize: none; min-height: 22px; max-height: 120px; line-height: 1.5; color: #1F2937; } .csk-input::placeholder { color: #9CA3AF; } .csk-root .csk-input:focus-visible { outline: none; } .csk-send-btn { display: flex; align-items: center; justify-content: center; width: 36px; height: 36px; min-width: 36px; border: none; border-radius: 10px; background: var(--csk-bg-user); color: #fff; cursor: pointer; transition: transform 0.18s, box-shadow 0.18s, opacity 0.18s; box-shadow: 0 3px 10px rgba(var(--csk-primary-rgb), 0.3); } .csk-send-btn:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 5px 14px rgba(var(--csk-primary-rgb), 0.4); } .csk-send-btn:active:not(:disabled) { transform: scale(0.92); } .csk-send-btn:disabled { background: #D1D5DB; box-shadow: none; cursor: not-allowed; opacity: 0.7; } .csk-send-btn svg { width: 18px; height: 18px; } /* ========== 保密声明脚注 ========== */ .csk-disclaimer { margin-top: 8px; padding: 6px 8px 4px; font-size: 10px; color: #9CA3AF; line-height: 1.5; user-select: none; } .csk-disclaimer__title { font-weight: 600; margin-bottom: 2px; color: #9CA3AF; } .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; } .csk-category-select { flex: 1; padding: 6px 28px 6px 10px; border: 1px solid var(--csk-border); border-radius: 8px; font-size: 12.5px; color: #374151; background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239CA3AF' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E") no-repeat right 9px center; appearance: none; -webkit-appearance: none; outline: none; cursor: pointer; font-family: inherit; transition: border-color 0.2s, box-shadow 0.2s; max-width: 220px; } .csk-category-select:focus { border-color: var(--csk-primary); box-shadow: 0 0 0 3px rgba(var(--csk-primary-rgb), 0.12); } /* ========== P1: RAG 引用来源卡片 ========== */ .csk-sources { margin-top: 8px; border: 1px solid var(--csk-border); border-radius: 12px; overflow: hidden; font-size: 12px; max-width: 100%; background: #FAFBFC; } .csk-sources__header { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; background: #F3F4F7; cursor: pointer; user-select: none; transition: background 0.15s; } .csk-sources__header:hover { background: #ECEEF2; } .csk-sources__title { display: flex; align-items: center; gap: 5px; font-weight: 500; color: #374151; } .csk-sources__arrow { transition: transform 0.2s; color: #9CA3AF; font-size: 10px; } .csk-sources--collapsed .csk-sources__arrow { transform: rotate(-90deg); } .csk-sources__body { border-top: 1px solid var(--csk-border); padding: 0; } .csk-sources--collapsed .csk-sources__body { display: none; } .csk-source-item { padding: 9px 12px; border-bottom: 1px solid #F0F1F4; transition: background 0.15s; } .csk-source-item:last-child { border-bottom: none; } .csk-source-item:hover { background: #fff; } .csk-source-item__name { font-weight: 500; color: #1F2937; margin-bottom: 3px; } .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: 4px; } /* ========== P1: Markdown 渲染样式 ========== */ .csk-msg--ai .csk-msg__bubble .csk-md-p { margin: 0 0 8px; } .csk-msg--ai .csk-msg__bubble .csk-md-p:last-child { margin-bottom: 0; } .csk-msg--ai .csk-msg__bubble .csk-md-h1, .csk-msg--ai .csk-msg__bubble .csk-md-h2, .csk-msg--ai .csk-msg__bubble .csk-md-h3, .csk-msg--ai .csk-msg__bubble .csk-md-h4, .csk-msg--ai .csk-msg__bubble .csk-md-h5, .csk-msg--ai .csk-msg__bubble .csk-md-h6 { margin: 14px 0 6px; font-weight: 600; line-height: 1.3; color: #111827; } .csk-msg--ai .csk-msg__bubble .csk-md-h1 { font-size: 20px; } .csk-msg--ai .csk-msg__bubble .csk-md-h2 { font-size: 17px; } .csk-msg--ai .csk-msg__bubble .csk-md-h3 { font-size: 15px; } .csk-msg--ai .csk-msg__bubble .csk-md-h4 { font-size: 14px; } .csk-md-code-block { position: relative; background: #1E293B; color: #E2E8F0; padding: 12px 14px; border-radius: 10px; overflow-x: auto; margin: 8px 0; font-size: 13px; line-height: 1.55; font-family: 'SF Mono', 'Consolas', 'Menlo', 'Monaco', monospace; } .csk-md-code-block code { background: none; padding: 0; border-radius: 0; font-size: inherit; color: inherit; } .csk-md-inline-code { background: rgba(var(--csk-primary-rgb), 0.1); color: var(--csk-primary); padding: 1px 6px; border-radius: 5px; font-size: 13px; font-family: 'SF Mono', 'Consolas', 'Menlo', 'Monaco', monospace; } .csk-msg--ai .csk-msg__bubble .csk-md-ul, .csk-msg--ai .csk-msg__bubble .csk-md-ol { padding-left: 22px; margin: 6px 0; } .csk-msg--ai .csk-msg__bubble .csk-md-ul li, .csk-msg--ai .csk-msg__bubble .csk-md-ol li { margin-bottom: 4px; } .csk-md-blockquote { border-left: 3px solid var(--csk-primary); padding: 2px 12px; margin: 8px 0; color: #6B7280; background: rgba(var(--csk-primary-rgb), 0.05); border-radius: 0 6px 6px 0; } .csk-md-link { color: var(--csk-primary); text-decoration: none; border-bottom: 1px solid rgba(var(--csk-primary-rgb), 0.3); transition: border-color 0.15s; } .csk-md-link:hover { border-bottom-color: var(--csk-primary); } .csk-md-hr { border: none; border-top: 1px solid var(--csk-border); margin: 12px 0; } .csk-md-table-wrap { max-width: 100%; overflow-x: auto; margin: 10px 0; border: 1px solid var(--csk-border); border-radius: 8px; } .csk-md-table { width: 100%; min-width: 360px; border-collapse: collapse; font-size: 12.5px; line-height: 1.5; } .csk-md-table th, .csk-md-table td { padding: 7px 9px; border-right: 1px solid var(--csk-border); border-bottom: 1px solid var(--csk-border); text-align: left; vertical-align: top; } .csk-md-table th:last-child, .csk-md-table td:last-child { border-right: none; } .csk-md-table tbody tr:last-child td { border-bottom: none; } .csk-md-table th { background: rgba(var(--csk-primary-rgb), 0.07); color: #374151; font-weight: 600; } .csk-md-table tbody tr:nth-child(even) { background: rgba(148, 163, 184, 0.06); } .csk-md-table .csk-md-align-left { text-align: left; } .csk-md-table .csk-md-align-center { text-align: center; } .csk-md-table .csk-md-align-right { text-align: right; } /* ========== 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; } .csk-quick-reply { border: 1px solid var(--csk-border); background: #fff; color: #374151; font-size: 12.5px; padding: 7px 13px; border-radius: 999px; cursor: pointer; font-family: inherit; line-height: 1.3; transition: all 0.18s ease; max-width: 100%; } .csk-quick-reply:hover { border-color: var(--csk-primary); color: var(--csk-primary); background: rgba(var(--csk-primary-rgb), 0.06); transform: translateY(-1px); } .csk-quick-reply:active { transform: scale(0.96); } /* ========== 推荐问题(suggest-message-list) ========== */ .csk-suggestions { margin-top: 10px; padding: 8px 0 0 0; border-top: 1px solid var(--csk-border); } .csk-suggestions__label { font-size: 12px; color: #9CA3AF; margin-bottom: 6px; } .csk-suggestions__list { display: flex; flex-wrap: wrap; gap: 6px; } .csk-suggestion-item { border: 1px solid var(--csk-border); background: #fff; color: #374151; font-size: 12px; padding: 6px 12px; border-radius: 999px; cursor: pointer; font-family: inherit; line-height: 1.3; transition: all 0.18s ease; max-width: 100%; text-align: left; } .csk-suggestion-item:hover { border-color: var(--csk-primary); color: var(--csk-primary); background: rgba(var(--csk-primary-rgb), 0.06); transform: translateY(-1px); } .csk-suggestion-item:active { transform: scale(0.96); } /* ========== AI 消息操作条 ========== */ .csk-msg__actions { display: flex; gap: 2px; margin-top: 5px; padding: 0 2px; opacity: 0; transition: opacity 0.15s ease; } .csk-msg--ai:hover .csk-msg__actions { opacity: 1; } .csk-msg--streaming .csk-msg__actions { display: none; } .csk-action-btn { display: flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; background: transparent; color: #9CA3AF; border-radius: 6px; cursor: pointer; transition: all 0.15s ease; } .csk-action-btn:hover { background: #F3F4F6; color: #374151; } .csk-action-btn--done { color: #10B981; } .csk-action-btn:active { transform: scale(0.9); } /* ========== 流式打字光标 ========== */ .csk-caret { display: inline-block; width: 7px; height: 15px; margin-left: 2px; vertical-align: text-bottom; background: var(--csk-primary); border-radius: 1px; animation: csk-caret-blink 1s step-end infinite; } @keyframes csk-caret-blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } } /* ========== 代码块复制按钮 ========== */ .csk-md-code-copy { position: absolute; top: 8px; right: 8px; display: flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; background: rgba(255, 255, 255, 0.12); color: #CBD5E1; border-radius: 6px; cursor: pointer; opacity: 0; transition: all 0.15s ease; } .csk-md-code-block:hover .csk-md-code-copy { opacity: 1; } .csk-md-code-copy:hover { background: rgba(255, 255, 255, 0.22); color: #fff; } .csk-md-code-copy:active { transform: scale(0.9); } /* ========== 新消息提示按钮 ========== */ .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-send-btn--stop { background: #EF4444 !important; box-shadow: 0 3px 10px rgba(239, 68, 68, 0.35) !important; } .csk-send-btn--stop:hover:not(:disabled) { box-shadow: 0 5px 14px rgba(239, 68, 68, 0.45) !important; } /* ========== 窗口缩放拖拽手柄 ========== */ .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-bg-app: #141421; 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-msg__avatar--ai { background: var(--csk-bg-ai); color: var(--csk-primary); border-color: var(--csk-border); } .csk-dark .csk-input-area { background: #1A1A2E; border-top-color: var(--csk-border); } .csk-dark .csk-input-wrap { background: var(--csk-bg-app); border-color: var(--csk-border); } .csk-dark .csk-input-wrap--focus { background: #1A1A2E; border-color: var(--csk-primary); } .csk-dark .csk-input { color: #E2E8F0; } .csk-dark .csk-loading__avatar { background: var(--csk-bg-ai); border-color: var(--csk-border); } .csk-dark .csk-loading__bubble { background: var(--csk-bg-ai); border-color: var(--csk-border); } .csk-dark .csk-loading__dot { background: #6B7280; } .csk-dark .csk-category-bar { background: #1A1A2E; border-top-color: var(--csk-border); } .csk-dark .csk-category-select { background-color: var(--csk-bg-app); border-color: var(--csk-border); color: #E2E8F0; } .csk-dark .csk-sources { background: #1E1E2E; border-color: var(--csk-border); } .csk-dark .csk-sources__header { background: #25253A; } .csk-dark .csk-sources__header:hover { background: #2D2D48; } .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-action-btn:hover { background: #2D2D48; color: #E2E8F0; } .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-md-code-block { background: #0F0F1A; } .csk-dark .csk-md-blockquote { background: rgba(var(--csk-primary-rgb), 0.08); } .csk-dark .csk-md-link { color: var(--csk-primary-light); } .csk-dark .csk-md-table th { background: rgba(var(--csk-primary-rgb), 0.14); color: #E2E8F0; } .csk-dark .csk-md-table tbody tr:nth-child(even) { background: rgba(148, 163, 184, 0.05); } .csk-dark .csk-msg--ai .csk-msg__bubble { background: var(--csk-bg-ai); color: var(--csk-text-ai); border-color: var(--csk-border); } .csk-dark .csk-welcome__title { color: #E2E8F0; } .csk-dark .csk-welcome__desc { color: #6B7280; } .csk-dark .csk-quick-reply { background: var(--csk-bg-app); border-color: var(--csk-border); color: #CBD5E1; } .csk-dark .csk-quick-reply:hover { background: rgba(var(--csk-primary-rgb), 0.12); border-color: var(--csk-primary); color: var(--csk-primary-light); } .csk-dark .csk-suggestion-item { background: var(--csk-bg-app); border-color: var(--csk-border); color: #CBD5E1; } .csk-dark .csk-suggestion-item:hover { background: rgba(var(--csk-primary-rgb), 0.12); border-color: var(--csk-primary); color: var(--csk-primary-light); } .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-feedback-btn { background: rgba(var(--csk-primary-rgb), 0.1); color: #9CA3AF; } .csk-dark .csk-feedback-btn:hover { background: rgba(var(--csk-primary-rgb), 0.2); color: var(--csk-primary-light); } .csk-dark .csk-feedback-btn--active { background: rgba(var(--csk-primary-rgb), 0.28); color: var(--csk-primary-light); } .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); } .csk-dark .csk-role-select option { background: #1A1A2E; color: #E2E8F0; } /* 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-btn { display: flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; background: rgba(var(--csk-primary-rgb), 0.08); color: #9CA3AF; border-radius: 6px; cursor: pointer; transition: all 0.15s ease; padding: 0; } .csk-feedback-btn:hover { background: rgba(var(--csk-primary-rgb), 0.18); color: var(--csk-primary); } .csk-feedback-btn--active { background: rgba(var(--csk-primary-rgb), 0.28); color: var(--csk-primary); } .csk-feedback-btn--active.down { background: #FEF2F2; color: #EF4444; } .csk-feedback-btn:active { transform: scale(0.9); } .csk-msg--streaming .csk-feedback-btn { display: none; } /* ========== 历史会话搜索框 ========== */ .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()); }